Android 5.x SwitchPreference is not behaving the same as in Android 4.x - android-5.0-lollipop

I have a code using SwitchPreference that used to work with Android 4.x however it no longer works since I updated my device to Android 5.0.1.
I have a simple SwitchPreference which displays a title on the left and an ON/OFF switch on the right.
<SwitchPreference
android:key="myPref"
android:selectable="true"
android:title="Title"
android:fragment="com.myApp.DeviceMonitorPrefsActivity"
android:switchTextOn="ON"
android:switchTextOff="OFF"/>
On the PreferenceActivity, I overrode onPreferenceTreeClick() to perform an Action (launching a setup Activity in my case) when I click on the title of this SwitchPreference control.
#Override
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference)
{
if(preference instanceof SwitchPreference){
// My Action
}
}
With Android 4.4.4, this Action used to be executed only when I pressed to the left of this control (title), but not when I changed the switch state.
Now with Android 5.0.1, the onPreferenceTreeClick() is called even when I change the switch state, and I didn't find a way to differentiate the two cases.
Is it a bug in Android 5.0.1 or is there a way to make this work cleanly?

This workaround found here seems to work : https://code.google.com/p/android/issues/detail?can=2&start=0&num=100&q=&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars&groupby=&sort=&id=172425
Here's my implementation that work on my case :
public class MySwitchPreference extends SwitchPreference {
/**
* Construct a new SwitchPreference with the given style options.
*
* #param context The Context that will style this preference
* #param attrs Style attributes that differ from the default
* #param defStyle Theme attribute defining the default style options
*/
public MySwitchPreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/**
* Construct a new SwitchPreference with the given style options.
*
* #param context The Context that will style this preference
* #param attrs Style attributes that differ from the default
*/
public MySwitchPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* Construct a new SwitchPreference with default style options.
*
* #param context The Context that will style this preference
*/
public MySwitchPreference(Context context) {
super(context, null);
}
#Override
protected void onBindView(View view) {
ViewGroup viewGroup= (ViewGroup)view;
setSwitchClickable(viewGroup);
super.onBindView(view);
}
private void setSwitchClickable(ViewGroup viewGroup) {
if (null == viewGroup) {
return;
}
int count = viewGroup.getChildCount();
for(int n = 0; n < count; ++n) {
View childView = viewGroup.getChildAt(n);
if(childView instanceof Switch) {
final Switch switchView = (Switch) childView;
switchView.setClickable(true);
return;
} else if (childView instanceof ViewGroup){
ViewGroup childGroup = (ViewGroup)childView;
setSwitchClickable(childGroup);
}
}
}
Then you just have to use your own "MySwitchPreference" into SwitchPreference directly.

Related

Libgdx | Custom Action

How can I create a custom action for an actor in libgdx? If I can't, than is there at least an action to run a custom piece of code (eg. call a method action)? Thanks.
EDIT:
I created this class :
class GapSizeAction extends TemporalAction {
private float newSize;
private Blocker blocker;
public static GapSizeAction getRotateAction(float newSize, float duration) {
return new GapSizeAction(newSize, duration);
}
public GapSizeAction(float newSize, float duration) {
super(duration);
System.out.println("Construct");
this.blocker = (Blocker)target;
this.newSize = newSize;
}
private float start, end;
protected void begin() {
System.out.println("Begin");
start = blocker.gap;
}
protected void update(float percent) {
blocker.gap = (start + (end - start) * percent);
}
}
The problem is that I am using a custom actor with a gap member (float). I try to cast the target to a blocker so that I can access the gap member variable, but gap ends up being null. I can confirm that gap is not null, I initialize it in the constructor. The blocker (Custom actor) is not null either. Am I going about this wrong?
Your problem is the line this.blocker = (Blocker)target; in your constructor. When the constructor is called, the action hasn't been set on a target yet, so target is null (and so will be blocker). Also, since you're changing a single float, you can extend FloatAction and save yourself some code. I would write your class as below. The constructor should be empty to support easy pooling, and you can set it up in your static factory method.
class GapSizeAction extends FloatAction {
public static GapSizeAction getRotateAction(float newSize, float duration){
GapSizeAction action = Actions.action(GapSizeAction.class);
action.setEnd(newSize);
action.setDuration(duration);
return action;
}
protected void begin () {
if (target instanceof Blocker)
setStart(((Blocker)target).gap);
else
Gdx.app.logError("Target is not a blocker: " + target.toString());
super.begin();
}
protected void update (float percent) {
super.update(percent);
if (target instanceof Blocker)
((Blocker)target).gap = getValue();
}
}
Fade In Action for example :
actor.AddAction(Actions.fadeIn(2.0f));

Android HTML5 video fullscreen and rotation

i read the article
Android WebView: handling orientation changes
and follow the tip i build a project
MainActivity
package com.example.testvideo1;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.ActionBar;
import android.support.v4.app.Fragment;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.webkit.WebView;
import android.os.Build;
#SuppressLint("NewApi")
public class MainActivity extends ActionBarActivity {
private VideoEnabledWebView webView;
private VideoEnabledWebChromeClient webChromeClient;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set layout
setContentView(R.layout.activity_main);
// Save the web view
webView = (VideoEnabledWebView) findViewById(R.id.webView);
// Initialize the VideoEnabledWebChromeClient and set event handlers
View nonVideoLayout = findViewById(R.id.nonVideoLayout);
// Your own view,
//Your own view,
ViewGroup videoLayout = (ViewGroup) findViewById(R.id.videoLayout);
View loadingView = null; // Your own view, read class comments
webChromeClient = new VideoEnabledWebChromeClient(nonVideoLayout,
videoLayout, loadingView, webView) // See all available
// constructors...
{
// Subscribe to standard events, such as onProgressChanged()...
#Override
public void onProgressChanged(WebView view, int progress) {
// Your code...
}
};
webChromeClient
.setOnToggledFullscreen(new VideoEnabledWebChromeClient.ToggledFullscreenCallback() {
#Override
public void toggledFullscreen(boolean fullscreen) {
// Your code to handle the full-screen change, for
// example showing and hiding the title bar. Example:
if (fullscreen) {
WindowManager.LayoutParams attrs = getWindow()
.getAttributes();
attrs.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN;
attrs.flags |= WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
getWindow().setAttributes(attrs);
if (android.os.Build.VERSION.SDK_INT >= 14) {
getWindow()
.getDecorView()
.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LOW_PROFILE);
}
} else {
WindowManager.LayoutParams attrs = getWindow()
.getAttributes();
attrs.flags &= ~WindowManager.LayoutParams.FLAG_FULLSCREEN;
attrs.flags &= ~WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
getWindow().setAttributes(attrs);
if (android.os.Build.VERSION.SDK_INT >= 14) {
getWindow().getDecorView()
.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_VISIBLE);
}
}
}
});
webView.setWebChromeClient(webChromeClient);
// Navigate everywhere you want, this classes have only been tested on
// YouTube's mobile site
webView.loadUrl("http://app.vlooks.cn/webchat/html5/2300/home");
}
#Override
public void onBackPressed() {
// Notify the VideoEnabledWebChromeClient, and handle it ourselves if it
// doesn't handle it
if (!webChromeClient.onBackPressed()) {
if (webView.canGoBack()) {
webView.goBack();
} else {
// Close app (presumably)
super.onBackPressed();
}
}
}
}
follow is VideoEnabledWebChromeClient.java
package com.example.testvideo1;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
import android.media.MediaPlayer.OnPreparedListener;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.webkit.WebChromeClient;
import android.widget.FrameLayout;
/**
* This class serves as a WebChromeClient to be set to a WebView, allowing it to
* play video. Video will play differently depending on target API level
* (in-line, fullscreen, or both).
*
* It has been tested with the following video classes: -
* android.widget.VideoView (typically API level <11) -
* android.webkit.HTML5VideoFullScreen$VideoSurfaceView/VideoTextureView
* (typically API level 11-18) -
* com.android.org.chromium.content.browser.ContentVideoView$VideoSurfaceView
* (typically API level 19+)
*
* Important notes: - For API level 11+, android:hardwareAccelerated="true" must
* be set in the application manifest. - The invoking activity must call
* VideoEnabledWebChromeClient's onBackPressed() inside of its own
* onBackPressed(). - Tested in Android API levels 8-19. Only tested on
* http://m.youtube.com.
*
* #author Cristian Perez (http://cpr.name)
*
*/
public class VideoEnabledWebChromeClient extends WebChromeClient implements
OnPreparedListener, OnCompletionListener, OnErrorListener {
public interface ToggledFullscreenCallback {
public void toggledFullscreen(boolean fullscreen);
}
private View activityNonVideoView;
private ViewGroup activityVideoView;
private View loadingView;
private VideoEnabledWebView webView;
private boolean isVideoFullscreen; // Indicates if the video is being
// displayed using a custom view
// (typically full-screen)
private FrameLayout videoViewContainer;
private CustomViewCallback videoViewCallback;
private ToggledFullscreenCallback toggledFullscreenCallback;
/**
* Never use this constructor alone. This constructor allows this class to
* be defined as an inline inner class in which the user can override
* methods
*/
#SuppressWarnings("unused")
public VideoEnabledWebChromeClient() {
}
/**
* Builds a video enabled WebChromeClient.
*
* #param activityNonVideoView
* A View in the activity's layout that contains every other view
* that should be hidden when the video goes full-screen.
* #param activityVideoView
* A ViewGroup in the activity's layout that will display the
* video. Typically you would like this to fill the whole layout.
*/
#SuppressWarnings("unused")
public VideoEnabledWebChromeClient(View activityNonVideoView,
ViewGroup activityVideoView) {
this.activityNonVideoView = activityNonVideoView;
this.activityVideoView = activityVideoView;
this.loadingView = null;
this.webView = null;
this.isVideoFullscreen = false;
}
/**
* Builds a video enabled WebChromeClient.
*
* #param activityNonVideoView
* A View in the activity's layout that contains every other view
* that should be hidden when the video goes full-screen.
* #param activityVideoView
* A ViewGroup in the activity's layout that will display the
* video. Typically you would like this to fill the whole layout.
* #param loadingView
* A View to be shown while the video is loading (typically only
* used in API level <11). Must be already inflated and without a
* parent view.
*/
#SuppressWarnings("unused")
public VideoEnabledWebChromeClient(View activityNonVideoView,
ViewGroup activityVideoView, View loadingView) {
this.activityNonVideoView = activityNonVideoView;
this.activityVideoView = activityVideoView;
this.loadingView = loadingView;
this.webView = null;
this.isVideoFullscreen = false;
}
/**
* Builds a video enabled WebChromeClient.
*
* #param activityNonVideoView
* A View in the activity's layout that contains every other view
* that should be hidden when the video goes full-screen.
* #param activityVideoView
* A ViewGroup in the activity's layout that will display the
* video. Typically you would like this to fill the whole layout.
* #param loadingView
* A View to be shown while the video is loading (typically only
* used in API level <11). Must be already inflated and without a
* parent view.
* #param webView
* The owner VideoEnabledWebView. Passing it will enable the
* VideoEnabledWebChromeClient to detect the HTML5 video ended
* event and exit full-screen. Note: The web page must only
* contain one video tag in order for the HTML5 video ended event
* to work. This could be improved if needed (see Javascript
* code).
*/
public VideoEnabledWebChromeClient(View activityNonVideoView,
ViewGroup activityVideoView, View loadingView,
VideoEnabledWebView webView) {
this.activityNonVideoView = activityNonVideoView;
this.activityVideoView = activityVideoView;
this.loadingView = loadingView;
this.webView = webView;
this.isVideoFullscreen = false;
}
/**
* Indicates if the video is being displayed using a custom view (typically
* full-screen)
*
* #return true it the video is being displayed using a custom view
* (typically full-screen)
*/
public boolean isVideoFullscreen() {
return isVideoFullscreen;
}
/**
* Set a callback that will be fired when the video starts or finishes
* displaying using a custom view (typically full-screen)
*
* #param callback
* A VideoEnabledWebChromeClient.ToggledFullscreenCallback
* callback
*/
public void setOnToggledFullscreen(ToggledFullscreenCallback callback) {
this.toggledFullscreenCallback = callback;
}
#Override
public void onShowCustomView(View view, CustomViewCallback callback) {
if (view instanceof FrameLayout) {
// A video wants to be shown
FrameLayout frameLayout = (FrameLayout) view;
View focusedChild = frameLayout.getFocusedChild();
// Save video related variables
this.isVideoFullscreen = true;
this.videoViewContainer = frameLayout;
this.videoViewCallback = callback;
// Hide the non-video view, add the video view, and show it
activityNonVideoView.setVisibility(View.INVISIBLE);
activityVideoView.addView(videoViewContainer, new LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
activityVideoView.setVisibility(View.VISIBLE);
if (focusedChild instanceof android.widget.VideoView) {
// android.widget.VideoView (typically API level <11)
android.widget.VideoView videoView = (android.widget.VideoView) focusedChild;
// Handle all the required events
videoView.setOnPreparedListener(this);
videoView.setOnCompletionListener(this);
videoView.setOnErrorListener(this);
} else {
// Other classes, including:
// - android.webkit.HTML5VideoFullScreen$VideoSurfaceView, which
// inherits from android.view.SurfaceView (typically API level
// 11-18)
// - android.webkit.HTML5VideoFullScreen$VideoTextureView, which
// inherits from android.view.TextureView (typically API level
// 11-18)
// -
// com.android.org.chromium.content.browser.ContentVideoView$VideoSurfaceView,
// which inherits from android.view.SurfaceView (typically API
// level 19+)
// Handle HTML5 video ended event only if the class is a
// SurfaceView
// Test case: TextureView of Sony Xperia T API level 16 doesn't
// work fullscreen when loading the javascript below
if (webView != null
&& webView.getSettings().getJavaScriptEnabled()
&& focusedChild instanceof SurfaceView) {
// Run javascript code that detects the video end and
// notifies the Javascript interface
String js = "javascript:";
js += "var _ytrp_html5_video_last;";
js += "var _ytrp_html5_video = document.getElementsByTagName('video')[0];";
js += "if (_ytrp_html5_video != undefined && _ytrp_html5_video != _ytrp_html5_video_last) {";
{
js += "_ytrp_html5_video_last = _ytrp_html5_video;";
js += "function _ytrp_html5_video_ended() {";
{
js += "_VideoEnabledWebView.notifyVideoEnd();"; // Must
// match
// Javascript
// interface
// name
// and
// method
// of
// VideoEnableWebView
}
js += "}";
js += "_ytrp_html5_video.addEventListener('ended', _ytrp_html5_video_ended);";
}
js += "}";
webView.loadUrl(js);
}
}
// Notify full-screen change
if (toggledFullscreenCallback != null) {
toggledFullscreenCallback.toggledFullscreen(true);
}
}
}
#Override
#SuppressWarnings("deprecation")
public void onShowCustomView(View view, int requestedOrientation,
CustomViewCallback callback) // Available in API level 14+,
// deprecated in API level 18+
{
onShowCustomView(view, callback);
}
#Override
public void onHideCustomView() {
// This method should be manually called on video end in all cases
// because it's not always called automatically.
// This method must be manually called on back key press (from this
// class' onBackPressed() method).
if (isVideoFullscreen) {
// Hide the video view, remove it, and show the non-video view
activityVideoView.setVisibility(View.INVISIBLE);
activityVideoView.removeView(videoViewContainer);
activityNonVideoView.setVisibility(View.VISIBLE);
// Call back (only in API level <19, because in API level 19+ with
// chromium webview it crashes)
if (videoViewCallback != null
&& !videoViewCallback.getClass().getName()
.contains(".chromium.")) {
videoViewCallback.onCustomViewHidden();
}
// Reset video related variables
isVideoFullscreen = false;
videoViewContainer = null;
videoViewCallback = null;
// Notify full-screen change
if (toggledFullscreenCallback != null) {
toggledFullscreenCallback.toggledFullscreen(false);
}
}
}
#Override
public View getVideoLoadingProgressView() // Video will start loading, only
// called in the case of
// VideoView (typically API
// level 10-)
{
if (loadingView == null) {
return super.getVideoLoadingProgressView();
} else {
loadingView.setVisibility(View.VISIBLE);
return loadingView;
}
}
#Override
public void onPrepared(MediaPlayer mp) // Video will start playing, only
// called in the case of
// android.widget.VideoView
// (typically API level <11)
{
if (loadingView != null) {
loadingView.setVisibility(View.GONE);
}
}
#Override
public void onCompletion(MediaPlayer mp) // Video finished playing, only
// called in the case of
// android.widget.VideoView
// (typically API level <11)
{
onHideCustomView();
}
#Override
public boolean onError(MediaPlayer mp, int what, int extra) // Error while
// playing
// video, only
// called in the
// case of
// android.widget.VideoView
// (typically
// API level
// <11)
{
return false; // By returning false, onCompletion() will be called
}
/**
* Notifies the class that the back key has been pressed by the user. This
* must be called from the Activity's onBackPressed(), and if it returns
* false, the activity itself should handle it. Otherwise don't do anything.
*
* #return Returns true if the event was handled, and false if was not
* (video view is not visible)
*/
public boolean onBackPressed() {
if (isVideoFullscreen) {
onHideCustomView();
return true;
} else {
return false;
}
}
}
and VideoEnabledWebView.java
package com.example.testvideo1;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import java.util.Map;
/**
* This class serves as a WebView to be used in conjunction with a
* VideoEnabledWebChromeClient. It makes possible: - To detect the HTML5 video
* ended event so that the VideoEnabledWebChromeClient can exit full-screen.
*
* Important notes: - Javascript is enabled by default and must not be disabled
* with getSettings().setJavaScriptEnabled(false). - setWebChromeClient() must
* be called before any loadData(), loadDataWithBaseURL() or loadUrl() method.
*
* #author Cristian Perez (http://cpr.name)
*
*/
public class VideoEnabledWebView extends WebView {
public class JavascriptInterface {
#android.webkit.JavascriptInterface
public void notifyVideoEnd() // Must match Javascript interface method
// of VideoEnabledWebChromeClient
{
// This code is not executed in the UI thread, so we must force that
// to happen
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
if (videoEnabledWebChromeClient != null) {
videoEnabledWebChromeClient.onHideCustomView();
}
}
});
}
}
private VideoEnabledWebChromeClient videoEnabledWebChromeClient;
private boolean addedJavascriptInterface;
public VideoEnabledWebView(Context context) {
super(context);
addedJavascriptInterface = false;
}
#SuppressWarnings("unused")
public VideoEnabledWebView(Context context, AttributeSet attrs) {
super(context, attrs);
addedJavascriptInterface = false;
}
#SuppressWarnings("unused")
public VideoEnabledWebView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
addedJavascriptInterface = false;
}
/**
* Indicates if the video is being displayed using a custom view (typically
* full-screen)
*
* #return true it the video is being displayed using a custom view
* (typically full-screen)
*/
public boolean isVideoFullscreen() {
return videoEnabledWebChromeClient != null
&& videoEnabledWebChromeClient.isVideoFullscreen();
}
/**
* Pass only a VideoEnabledWebChromeClient instance.
*/
#Override
#SuppressLint("SetJavaScriptEnabled")
public void setWebChromeClient(WebChromeClient client) {
getSettings().setJavaScriptEnabled(true);
if (client instanceof VideoEnabledWebChromeClient) {
this.videoEnabledWebChromeClient = (VideoEnabledWebChromeClient) client;
}
super.setWebChromeClient(client);
}
#Override
public void loadData(String data, String mimeType, String encoding) {
addJavascriptInterface();
super.loadData(data, mimeType, encoding);
}
#Override
public void loadDataWithBaseURL(String baseUrl, String data,
String mimeType, String encoding, String historyUrl) {
addJavascriptInterface();
super.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl);
}
#Override
public void loadUrl(String url) {
addJavascriptInterface();
super.loadUrl(url);
}
#Override
public void loadUrl(String url, Map<String, String> additionalHttpHeaders) {
addJavascriptInterface();
super.loadUrl(url, additionalHttpHeaders);
}
private void addJavascriptInterface() {
if (!addedJavascriptInterface) {
// Add javascript interface to be called when the video ends (must
// be done before page load)
addJavascriptInterface(new JavascriptInterface(),
"_VideoEnabledWebView"); // Must match Javascript interface
// name of
// VideoEnabledWebChromeClient
addedJavascriptInterface = true;
}
}
}
and the layout file
<!-- View that will be hidden when video goes fullscreen -->
<RelativeLayout
android:id="#+id/nonVideoLayout"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.example.VideoEnabledWebView
android:id="#+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
<!-- View where the video will be shown when video goes fullscreen -->
<RelativeLayout
android:id="#+id/videoLayout"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<!-- View that will be shown while the fullscreen video loads (maybe include a spinner and a "Loading..." message) -->
<View
android:id="#+id/videoLoading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="invisible" />
</RelativeLayout>
the project can't work, and i want when fullscreen , let the program go LandScape mode.
but i failed.
thanks.......
I've been facing the same issue.
Although this question is quite old, I have come to a simple solution which I could not find on stack overflow.
Just enforce Landscape when going full screen:
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
And revert this, when going back.
In context:
public class BaseWebChromeClient extends WebChromeClient
{
#Override
public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback)
{
[...]
act.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
[...]
#Override
public void onHideCustomView(View view, WebChromeClient.CustomViewCallback callback)
{
[...]
act.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
// use SCREEN_ORIENTATION_SENSOR, if you don't to enforce portrait mode.
[...]
}
}

How pass class reference by using deluxe signals in AS3Signals?

I wanna pass class reference when dispatching the deluxe signals in AS3Signals ?
My code here for dispatch,
public var signal:DeluxeSignal = new DeluxeSignal(this);
protected function button1_clickHandler(event:MouseEvent):void
{
signal.dispatch(new GenericEvent());
}
and here i listen,
protected function creComp(event:FlexEvent):void
{
viewB.signal.add(onDeluxDispatched);
}
private function onDeluxDispatched(e:GenericEvent):void
{
trace(e.target, e.signal);
trace(e.currentTarget);
trace("SignalTest.onDeluxDispatched(e)");
}
But i received null in trace.
where i am wrong ?
from the documentation of DeluxeSignal class ( https://github.com/robertpenner/as3-signals/blob/master/src/org/osflash/signals/DeluxeSignal.as )
/**
* Creates a DeluxeSignal instance to dispatch events on behalf of a target object.
* #param target The object the signal is dispatching events on behalf of.
* #param valueClasses Any number of class references that enable type checks in dispatch().
* For example, new DeluxeSignal(this, String, uint)
* would allow: signal.dispatch("the Answer", 42)
* but not: signal.dispatch(true, 42.5)
* nor: signal.dispatch()
*
* NOTE: Subclasses cannot call super.apply(null, valueClasses),
* but this constructor has logic to support super(valueClasses).
*/
public function DeluxeSignal(target:Object = null, ...valueClasses)
it has to be declared and dispatched that way :
public class ClassName {
public var signal:DeluxeSignal = new DeluxeSignal(this, ClassName);
private function dispatch():void {
signal.dispatch(this);
}
}
then retrieved that way in the referenced class :
public class Parent {
private var childClass:ClassName;
private function bindSignal() {
childClass.signal.add(signalListener);
}
private function signalListener(classReference:ClassName) {
/* do your stuff with classReference */
}
}
i ran into same issue and that worked for me
Depending on your requirements you may not need to use DeluxeSignal. I'll use Willo's example to illustrate.
public class Parent {
private var childClass:ClassName;
private function bindSignal() {
childClass.signal.add(signalListener);
}
private function signalListener(classReference:ClassName) {
/* do your stuff with classReference */
}
}
Yes, we get a class reference. But this class reference is not the one that was passed to DeluxeSignal when it was instantiated, but instead it is the one that was passed to dispatch.
public class ClassName {
/* this is not the reference to 'this' that the listener gets. in fact,
all this one does is sit inside the signal as a property */
public var signal:DeluxeSignal = new DeluxeSignal(this, ClassName);
private function dispatch():void {
signal.dispatch(this); // this is the reference that the listener gets
}
}
The reference we passed into the constructor just sits inside a public property called target.
public function DeluxeSignal(target:Object = null, ... valueClasses) {
/* the reference is set as _target and is not used anywhere else in the
class other than in the setter/getter */
_target = target;
valueClasses = (valueClasses.length == 1 && valueClasses[0] is Array) ? valueClasses[0] : valueClasses;
super(valueClasses);
}
public function get target():Object { return _target; }
public function set target(value:Object):void {
if (value == _target) return;
removeAll();
_target = value;
}
So it seems the idea is to access this property when the listener is called, using a reference to the signal:
public class Parent {
private var childClass:ClassName;
private function bindSignal() {
childClass.signal.add(signalListener);
}
private function signalListener() {
/* not passing the reference in the dispatch() call means we can
still access the target at this point by using... */
childClass.signal.target;
/* but it just feels nicer to have the reference provided as an
argument rather than as a mutable property, right? */
}
}
Technically that works but it doesn't feel great to be getting a reference from what is a mutable property.
Where it might come in handy though is when using a dependency injection framework like Robotlegs, where the signal is injected into a command. Your command wouldn't have a listener with which to be provided a reference in an argument, but it would be able to access the reference from the signal via its target property.
The way Willo used DeluxeSignal here though, you can actually use the plain old Signal class to do exactly the same thing with less overhead.
public class ClassName {
/* note: no pointless use of 'this' in the constructor. instead,
just the types we will actually be providing to the listener */
public var signal:Signal = new Signal(ClassName, String);
private function dispatch():void {
signal.dispatch(this, "Whatever else you want");
}
}
And on the other end you get this.
public class Parent {
private var childClass:ClassName;
private function bindSignal() {
childClass.signal.add(signalListener);
}
private function signalListener(classReference:ClassName, foo:String) {
/* do your stuff with classReference */
}
}
So you get the same result for typing less code and for less inheritance overhead.
Where you might want to use DeluxeSignal though, other than when needing to keep a reference to the target in your DI frameworks, is when you're using native events. DeluxeSignal overrides the dispatch method to do some extra work with those.

What is the best way to trigger a combo-box cell editor by typing in a JTable cell?

In other words, I want JTable to drop-down a combo-box whenever user types in a cell that has a JComboBox (or any other JComboBox-based cell-editor) editor associated to it.
Basically, you have to install an appropriate listener on the combo and open the popup explicitly. First candidate for "appropriate" is an AncestorListener, which invokes showing the popup in its ancestorAdded method.
Unfortunately that doesn't seem to be the whole story: works if the table's surrenderFocus property is false. If it is true works only for not-editable combos. After some digging, the reason for the not-working part turns out to be an internal focustransfer (from the combo to the textfield) after the popup is opened by the ancestorListener. In that case, we need a second listener which opens the popup once the editor's editingComponent got the focus permanently.
Multiple listeners routinely step onto each other's feet, so best to not install both permanently but do it on each call to getEditorComp, and let them uninstall themselves once they showed the popup. Below is a working example of how-to do it, just beware: it's not formally tested!
public static class DefaultCellEditorX extends DefaultCellEditor {
private AncestorListener ancestorListener;
private PropertyChangeListener focusPropertyListener;
public DefaultCellEditorX(JComboBox comboBox) {
super(comboBox);
}
/**
* Overridden to install an appriate listener which opens the
* popup when actually starting an edit.
*
* #inherited <p>
*/
#Override
public Component getTableCellEditorComponent(JTable table,
Object value, boolean isSelected, int row, int column) {
super.getTableCellEditorComponent(table, value, isSelected, row, column);
installListener(table);
return getComponent();
}
/**
* Shows popup.
*/
protected void showPopup() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
getComponent().setPopupVisible(true);
}
});
}
/**
* Dynamically install self-uninstalling listener, depending on JComboBox
* and JTable state.
* #param table
*/
private void installListener(JTable table) {
if (getComponent().isEditable() && table.getSurrendersFocusOnKeystroke()) {
installKeyboardFocusListener();
} else {
installAncestorListener();
}
}
private void installAncestorListener() {
if (ancestorListener == null) {
ancestorListener = new AncestorListener() {
#Override
public void ancestorAdded(AncestorEvent event) {
getComponent().removeAncestorListener(ancestorListener);
showPopup();
}
#Override
public void ancestorRemoved(AncestorEvent event) {
}
#Override
public void ancestorMoved(AncestorEvent event) {
}
};
}
getComponent().addAncestorListener(ancestorListener);
}
private void installKeyboardFocusListener() {
if (focusPropertyListener == null) {
focusPropertyListener = new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
LOG.info("property: " + evt.getPropertyName());
if (focusManager().getPermanentFocusOwner() !=
getComponent().getEditor().getEditorComponent()) return;
focusManager()
.removePropertyChangeListener("permanentFocusOwner", focusPropertyListener);
showPopup();
}
};
}
focusManager().addPropertyChangeListener("permanentFocusOwner", focusPropertyListener);
}
/**
* Convience for less typing.
* #return
*/
protected KeyboardFocusManager focusManager() {
return KeyboardFocusManager.getCurrentKeyboardFocusManager();
}
/**
* Convenience for type cast.
* #inherited <p>
*/
#Override
public JComboBox getComponent() {
return (JComboBox) super.getComponent();
}
}
JTable table = new JTable(data, columns);
table.putClientProperty("terminateEditOnFocusLost", true);
JScrollPane scrollPane = new JScrollPane(table);
final JXComboBox editorComboBox = new JXComboBox(array);
editorComboBox.addAncestorListener(new AncestorListener() {
public void ancestorAdded(AncestorEvent event) {
//make sure combobox handles key events
editorComboBox.requestFocusInWindow();
}
public void ancestorMoved(AncestorEvent event) {}
public void ancestorRemoved(AncestorEvent event) {}
});
AutoCompleteDecorator.decorate(editorComboBox);
TableColumn column = table.getColumnModel().getColumn(0);
column.setCellEditor(new ComboBoxCellEditor(editorComboBox));

After adding a TableRowSorter adding values to model cause java.lang.IndexOutOfBoundsException: Invalid range

After adding a TableRowSorter to a table and its corresponding model any corresponding adds specifically at firetabletablerowsinserted cause exceptions. It is clear from testing that the GetRowCount() is returning a value past the models range. However it does not make sense to me how to continue to add values to the table after a sorter or filter has been added?
As an example, I set the row filter before adding anything to the table then add a value to the table with the following calls in my model:
this.addRow(row, createRow(trans,row));
this.fireTableRowsInserted(this.getRowCount(), this.getRowCount());
The rowcount is of size 1 and the exception is thrown:
java.lang.IndexOutOfBoundsException: Invalid range
at javax.swing.DefaultRowSorter.checkAgainstModel(Unknown Source)
at javax.swing.DefaultRowSorter.rowsInserted(Unknown Source)
at com.gui.model
If I do the same steps without first adding the sorter everything is fine. I assumed that possibly I needed to notify the model that the sorter may have made changes and tried the following but still returns an exception:
this.addRow(row, createRow(trans,row));
this.fireTableStructureChanged()
this.fireTableRowsInserted(this.getRowCount(), this.getRowCount());
I even tried to notify the sorter inside the model that a value has been added to the model before calling fire like below but it fails as well:
this.addRow(row, createRow(trans,row));
if(sorter.getRowFilter() != null){
//if a sorter exists we are in add notify sorter
sorter.rowsInserted(getRowCount(), getRowCount());
}
this.fireTableRowsInserted(this.getRowCount(), this.getRowCount());
Lastly, I hard coded the FireTableRowsInsterted(0,0) and it does not throw any exception. But nothing gets added to table? So, I know it is definitely some type of OutOfBounds issue.
I have looked all over and cannot seem to find the answer. If anyone has any idea how this is suppose to work it be very helpful.
Here is code that sets the sorter inside jpanel:
messageTable.setRowSorter(null);
HttpTransactionTableModel m = getTransactionTableModel();
final int statusIndex = m.getColIndex("status");
RowFilter<Object,Object> startsWithAFilter = new RowFilter<Object,Object>() {
public boolean include(Entry<? extends Object, ? extends Object> entry) {
for(char responseCode:responseCodes)
{
if (entry.getStringValue(statusIndex).startsWith(Character.toString(responseCode))) {
return true;
}
}
// None of the columns start with "a"; return false so that this
// entry is not shown
return false;
}
};
m.sorter.setRowFilter(startsWithAFilter);
messageTable.setRowSorter(m.sorter);
Here is code inside my model that adds value to model:
public void update(Observable o, Object evt) {
if (evt instanceof ObservableEvent<?>) {
ObservableEvent<?> event = (ObservableEvent<?>) evt;
if (event.getElement() instanceof HttpTransaction) {
HttpTransaction trans = (HttpTransaction) event.getElement();
// handle adding of an element
if (event.getAction() == PUT) {
if (includeTransaction(trans)) {
// handle request elements
if (trans.getRequest() != null && idMap.get(trans.getID()) == null) {
idMap.put(trans.getID(), count++);
// transactionManager.save(trans);
int row = idMap.get(trans.getID());
this.addRow(row, createRow(trans,row));
if(sorter.getRowFilter() != null){
sorter.rowsInserted(getRowCount(), getRowCount());
}
this.fireTableRowsInserted(this.getRowCount(), this.getRowCount());
}
You have an out by 1 error. The correct code for firing the event is:
this.fireTableRowsInserted(this.getRowCount()-1, this.getRowCount()-1);
I went back and had a better look at this after seeing kleopatra's comment. I was changing my TableModel after creating a RowSorter, but before attaching the RowSorter to the JTable. Here's an example that shows the problem I was having.
import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableRowSorter;
import java.util.ArrayList;
import java.util.List;
public class TestTableMain {
public static void main(String[] args) {
new TestTableMain();
}
public TestTableMain() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
buildAndShowMainFrame();
}
});
}
private void buildAndShowMainFrame() {
JFrame frame = new JFrame();
JScrollPane scrollPane = new JScrollPane();
TestTableModel model = new TestTableModel();
JTable table = new JTable(model);
TableRowSorter<TestTableModel> rowSorter = new TableRowSorter<>(model);
rowSorter.setRowFilter(null);
model.add("First added item.");
/* The RowSorter doesn't observe the TableModel directly. Instead,
* the JTable observes the TableModel and notifies the RowSorter
* about changes. At this point, the RowSorter(s) internal variable
* modelRowCount is incorrect. There are two easy ways to fix this:
*
* 1. Don't add data to the model until the RowSorter has been
* attached to the JTable.
*
* 2. Notify the RowSorter about model changes just prior to
* attaching it to the JTable.
*/
// Uncomment the next line to notify rowSorter that you've changed
// the model it's using prior to attaching it to the table.
//rowSorter.modelStructureChanged();
table.setRowSorter(rowSorter);
scrollPane.setViewportView(table);
frame.setContentPane(scrollPane);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
model.add("Second added item.");
}
private class TestTableModel extends AbstractTableModel {
private List<String> items = new ArrayList<>();
public TestTableModel() {
for(int i=0;i<5;i++) {
add("Item " + i);
}
}
#Override
public int getRowCount() {
return items.size();
}
#Override
public int getColumnCount() {
return 1;
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
return items.get(rowIndex);
}
public void add(String item) {
items.add(item);
fireTableRowsInserted(items.size() - 1, items.size() - 1);
}
}
}
So, for now it looks like if you check in your model if your currently in sorting mode and if that is case only call update on sorting model. Otherwise call normal model fire updates everything seems to work so far. I'm still open for better ways to handle this though:
if(sorter.getRowFilter() != null){
sorter.modelStructureChanged();
}
else
this.fireTableRowsInserted(this.getRowCount(), this.getRowCount());