How to animate JavaFX background gradient with light colors - javafx

I am looking for the solution for light color background gradient animation. I have looked into other examples with KeyFrames and was unable to do it by myself.
So I made a primitive version of what I need:
boolean rbottomforward = true;
boolean gbottomforward = true;
boolean bbottomforward = true;
int redbottom = 171;
int greenbottom = 186;
int bluebottom = 171;
boolean rtopforward = true;
boolean gtopforward = true;
boolean btopforward = true;
int redtop = 255;
int greentop = 255;
int bluetop = 255;
#Override
protected Void call() throws Exception {
Timeline backgroundAnimator = new Timeline(new KeyFrame(Duration.millis(200), event -> {
backgroundPane.getStylesheets().clear();
backgroundPane.setStyle("-fx-background-color: linear-gradient(to top, "+ randomBottomColor()+", "+randomTopColor()+");");
}));
backgroundAnimator.setCycleCount(Timeline.INDEFINITE);
backgroundAnimator.play();
return null;
}
private String randomBottomColor() {
greenbottom = newBottomGreen();
redbottom = newBottomRed();
bluebottom = newBottomBlue();
return String.format("rgb(%d,%d,%d)", redbottom, greenbottom, bluebottom);
}
private int newBottomGreen() {
if(greenbottom <255 && greenbottom >155){
if(gbottomforward)
greenbottom +=getRandomNumber();
else
greenbottom -=getRandomNumber();
} else if(greenbottom >=255) {
gbottomforward=false;
greenbottom -=getRandomNumber();
} else {
gbottomforward=true;
greenbottom +=getRandomNumber();
}
return greenbottom;
}
private int getRandomNumber() {
int r = (int) (Math.random() * (3 - 1)) + 1;
return r;
}
private int newBottomRed() {
if(redbottom <255 && redbottom >155){
if(rbottomforward)
redbottom +=getRandomNumber();
else
redbottom -=getRandomNumber();
} else if(redbottom >=255) {
rbottomforward=false;
redbottom -=getRandomNumber();
} else {
rbottomforward=true;
redbottom +=getRandomNumber();
}
return redbottom;
}
private int newBottomBlue() {
if(bluebottom <255 && bluebottom >155){
if(bbottomforward)
bluebottom +=getRandomNumber();
else
bluebottom -=getRandomNumber();
} else if(bluebottom >=255) {
bbottomforward=false;
bluebottom -=getRandomNumber();
} else{
bbottomforward=true;
bluebottom +=getRandomNumber();
}
return bluebottom;
}
private String randomTopColor() {
greentop = newTopGreen();
redtop = newTopRed();
bluetop = newTopBlue();
return String.format("rgb(%d,%d,%d)", redtop, greentop, bluetop);
}
private int newTopGreen() {
if(greentop <255 && greentop >155){
if(gtopforward)
greentop +=getRandomNumber();
else
greentop -=getRandomNumber();
} else if(greentop >=255) {
gtopforward=false;
greentop -=getRandomNumber();
} else {
gtopforward=true;
greentop +=getRandomNumber();
}
return greentop;
}
private int newTopRed() {
if(redtop <255 && redtop >155){
if(rtopforward)
redtop +=getRandomNumber();
else
redtop -=getRandomNumber();
} else if(redtop >=255) {
rtopforward=false;
redtop -=getRandomNumber();
} else {
rtopforward=true;
redtop +=getRandomNumber();
}
return redtop;
}
private int newTopBlue() {
if(bluetop <255 && bluetop >155){
if(btopforward)
bluetop +=getRandomNumber();
else
bluetop -=getRandomNumber();
} else if(bluetop >=255) {
btopforward=false;
bluetop -=getRandomNumber();
} else{
btopforward=true;
bluetop +=getRandomNumber();
}
return bluetop;
}
This is a bit overkill. Can anyone help me convert this to a more advanced level code. Thanks :)

There's really not much you can do about this but reduce the repetition in code. (You won't be able to interpolate anything, since on every update the next color is chosen at random.)
You should use the same code for all the color channels:
/**
* Class containing logic for a single color channel.
*/
private class AnimatedChannel {
private boolean increment = true;
private int value;
AnimatedChannel(int value) {
this.value = value;
}
/**
* Changes channel by random amount in [1, 3] keeping it in range [155, 255]
*/
private void update() {
int delta = getRandomNumber();
if (increment) {
value += delta;
if (value > 0xFF) {
value = (2 * 0xFF) - value; // v = max - (v - max)
increment = false;
}
} else {
value -= delta;
if (value < 155) {
value = (2 * 155) - value; // v = min + (min - v)
increment = true;
}
}
}
}
private final Random random = new Random();
private int getRandomNumber() {
return random.nextInt(3 - 1) + 1;
}
private final AnimatedChannel bottomRed = new AnimatedChannel(171);
private final AnimatedChannel bottomGreen = new AnimatedChannel(186);
private final AnimatedChannel bottomBlue = new AnimatedChannel(171);
private final AnimatedChannel topRed = new AnimatedChannel(0xFF);
private final AnimatedChannel topGreen = new AnimatedChannel(0xFF);
private final AnimatedChannel topBlue = new AnimatedChannel(0xFF);
private final AnimatedChannel[] channels = new AnimatedChannel[] {
bottomRed,
bottomGreen,
bottomBlue,
topRed,
topGreen,
topBlue
};
#Override
protected Void call() throws Exception {
Timeline backgroundAnimator = new Timeline(new KeyFrame(Duration.millis(200), event -> {
for (AnimatedChannel channel : channels) {
channel.update();
}
backgroundPane.setBackground(new Background(new BackgroundFill(
new LinearGradient(0, 0, 0, 1, true, CycleMethod.NO_CYCLE,
new Stop(0, channelsToColor(topRed, topGreen, topBlue)),
new Stop(1, channelsToColor(bottomRed, bottomGreen, bottomBlue))),
CornerRadii.EMPTY,
Insets.EMPTY)));
}));
backgroundAnimator.setCycleCount(Timeline.INDEFINITE);
backgroundAnimator.play();
return null;
}
private static Color channelsToColor(AnimatedChannel red, AnimatedChannel green, AnimatedChannel blue) {
return Color.rgb(red.value, green.value, blue.value);
}
This code is not that much shorter than your code, but it's easier to maintain. If you want to modify the logic for updating the channels, you only need to adjust 1 method instead of 6 methods. Also it relieves JavaFX of the burden of parsing the inline CSS on every update.
Note:
You seem to be extending Task<Void> or some similar concurrency-related class. This is not really necessary, since Timeline.play is not a long running operation but simply triggers repeated execution of logic (in this case the EventHandler<ActionEvent>) on the application thread at a later time.

Related

Using pinch to zoomin and zoomout some unwanted background should copied color appeared and pixel quality low android below 9 version Xamarin android

Whenever i using wo fingers pinch to zoomin and zoomout some unwanted background should copied color appeared my camerapagerenderer if i single touch or touch it not seems to be apppear when i pinch to zoomin using two fingers it appears on my screen
public override bool OnTouchEvent(MotionEvent e)
{
switch (e.Action & MotionEventActions.Mask)
{
case MotionEventActions.Down:
oldDist = getFingerSpacing(e);
break;
case MotionEventActions.Move:
float newDist = getFingerSpacing(e);
if (newDist > oldDist)
{
//mCamera is your Camera which used to take picture, it should already exit in your custom Camera
handleZoom(true, camera);
}
else if (newDist < oldDist)
{
handleZoom(false, camera);
}
oldDist = newDist;
break;
}
return true;
}
private void handleZoom(bool isZoomIn, global::Android.Hardware.Camera camera)
{
global::Android.Hardware.Camera.Parameters parameters = camera.GetParameters();
if (parameters.IsZoomSupported)
{
int maxZoom = parameters.MaxZoom;
int zoom = parameters.Zoom;
if (isZoomIn && zoom < maxZoom)
{
zoom++;
}
else if(zoom > 0)
{
zoom--;
}
parameters.Zoom = zoom;
camera.SetParameters(parameters);
}
else
{
Android.Util.Log.Error("lv", "zoom not supported");
}
}
private static float getFingerSpacing(MotionEvent e)
{
if(e.PointerCount==2)
{
int pointerIndex = e.FindPointerIndex(_activePointerId);
float x = e.GetX(pointerIndex);
float y = e.GetY(pointerIndex);
return (float)Math.Sqrt(x * x + y * y);
}
}
You could check the code below. It works on Android 10.0 with no color shades.
class CameraPageRenderer : PageRenderer, TextureView.ISurfaceTextureListener
{
global::Android.Hardware.Camera camera;
global::Android.Widget.Button takePhotoButton;
global::Android.Widget.Button toggleFlashButton;
global::Android.Widget.Button switchCameraButton;
global::Android.Views.View view;
Activity activity;
CameraFacing cameraType;
TextureView textureView;
SurfaceTexture surfaceTexture;
bool flashOn;
public CameraPageRenderer(Context context) : base(context)
{
}
float oldDist = 1f;
public override bool OnTouchEvent(MotionEvent e)
{
switch (e.Action & MotionEventActions.Mask)
{
case MotionEventActions.Down:
oldDist = getFingerSpacing(e);
break;
case MotionEventActions.Move:
float newDist = getFingerSpacing(e);
if (newDist > oldDist)
{
//mCamera is your Camera which used to take picture, it should already exit in your custom Camera
handleZoom(true, camera);
}
else if (newDist < oldDist)
{
handleZoom(false, camera);
}
oldDist = newDist;
break;
}
return true;
}
private static float getFingerSpacing(MotionEvent e)
{
if (e.PointerCount == 2)
{
float x = e.GetX(0) - e.GetX(1);
float y = e.GetY(0) - e.GetY(1);
return (float)Math.Sqrt(x*x + y*y);
}
return 0;
}
private void handleZoom(bool isZoomIn, global::Android.Hardware.Camera camera)
{
//camera.StopPreview();
// camera.Release();
// camera = global::Android.Hardware.Camera.Open((int)cameraType);
global::Android.Hardware.Camera.Parameters parameters = camera.GetParameters();
if (parameters.IsZoomSupported)
{
int maxZoom = parameters.MaxZoom;
int zoom = parameters.Zoom;
if (isZoomIn && zoom < maxZoom)
{
zoom++;
}
else if (zoom > 0)
{
zoom--;
}
parameters.Zoom = zoom;
camera.SetParameters(parameters);
camera.SetPreviewTexture(surfaceTexture);
PrepareAndStartCamera();
}
else
{
Android.Util.Log.Error("lv", "zoom not supported");
}
}
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
if (e.OldElement != null || Element == null)
{
return;
}
try
{
SetupUserInterface();
//SetupEventHandlers();
AddView(view);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(#" ERROR: ", ex.Message);
}
}
void SetupUserInterface()
{
activity = this.Context as Activity;
view = activity.LayoutInflater.Inflate(Resource.Layout.CameraLayout, this, false);
cameraType = CameraFacing.Back;
textureView = view.FindViewById<TextureView>(Resource.Id.textureView);
textureView.SurfaceTextureListener = this;
}
protected override void OnLayout(bool changed, int l, int t, int r, int b)
{
base.OnLayout(changed, l, t, r, b);
var msw = MeasureSpec.MakeMeasureSpec(r - l, MeasureSpecMode.Exactly);
var msh = MeasureSpec.MakeMeasureSpec(b - t, MeasureSpecMode.Exactly);
view.Measure(msw, msh);
view.Layout(0, 0, r - l, b - t);
}
public void OnSurfaceTextureAvailable(SurfaceTexture surface, int width, int height)
{
camera = global::Android.Hardware.Camera.Open((int)cameraType);
textureView.LayoutParameters = new FrameLayout.LayoutParams(width, height);
surfaceTexture = surface;
camera.SetPreviewTexture(surface);
PrepareAndStartCamera();
}
public bool OnSurfaceTextureDestroyed(SurfaceTexture surface)
{
camera.StopPreview();
camera.Release();
return true;
}
public void OnSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height)
{
PrepareAndStartCamera();
}
public void OnSurfaceTextureUpdated(SurfaceTexture surface)
{
}
void PrepareAndStartCamera()
{
camera.StopPreview();
var display = activity.WindowManager.DefaultDisplay;
if (display.Rotation == SurfaceOrientation.Rotation0)
{
camera.SetDisplayOrientation(90);
}
if (display.Rotation == SurfaceOrientation.Rotation270)
{
camera.SetDisplayOrientation(180);
}
camera.StartPreview();
}
}
Update: The result on Android 6.0

Secondary Toolbar items not fitting the screen

I have a Xamarin.Forms app with FreshMvvm, and I use secondary ToolbarItems. To do it in iOS, I had to make a custom renderer (unlike in Android). I was given a solution on how to implement it here:
ToolbarItems do not look right in iOS
This solution works perfectly for me. But by now, the toolbar menu grew longer, and some of its elements do not fit the iPhone's screen. I can slide the menu and see all the elements, but as soon as I release the screen, the view jumps back up, and while it is held by a finger, the elements are not clickable. How can this be solved? Can the menu made to wrap, or something else?
On Android, the menu stays where I scroll and I can click every item. Can it made stay where scrolled on iOS, too?
Here is my renderer's code:
using CoreGraphics;
using MobileApp.iOS.Renderers;
using MobileApp.iOS.Services;
using MobileApp.Pages;
using MobileApp.Services;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(CustomToolbarContentPage),
typeof(RightToolbarMenuCustomRenderer))]
namespace MobileApp.iOS.Renderers
{
class RightToolbarMenuCustomRenderer : PageRenderer
{
private List<ToolbarItem> _primaryItems;
private List<ToolbarItem> _secondaryItems;
private UITableView _table;
private UITapGestureRecognizer _tapGestureRecognizer;
private UIView _transparentView;
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
if (e.NewElement is IAddToolbarItem item)
{
item.ToolbarItemAdded += Item_ToolbarItemAdded;
}
base.OnElementChanged(e);
}
private void Item_ToolbarItemAdded(object sender, System.EventArgs e)
{
if (Element is ContentPage page)
{
_primaryItems = page.ToolbarItems.Where(i => i.Order == ToolbarItemOrder.Primary).ToList();
_secondaryItems = page.ToolbarItems.Where(i => i.Order == ToolbarItemOrder.Secondary).ToList();
_secondaryItems.ForEach(t => page.ToolbarItems.Remove(t));
}
var element = (ContentPage)Element;
if (_secondaryItems?.Count == 0 && element.ToolbarItems.Any(a => (a.IconImageSource as FileImageSource)?.File == "more.png"))
{
element.ToolbarItems.Clear();
}
else if (_secondaryItems?.Count >= 1 && !element.ToolbarItems.Any(a => (a.IconImageSource as FileImageSource)?.File == "more.png"))
{
element.ToolbarItems.Add(new ToolbarItem()
{
Order = ToolbarItemOrder.Primary,
IconImageSource = "more.png",
Priority = 1,
Command = new Command(ToggleDropDownMenuVisibility)
});
}
}
private void ToggleDropDownMenuVisibility()
{
if (!DoesTableExist())
{
if ((View?.Subviews != null)
&& (View.Subviews.Length > 0)
&& (View.Bounds != null)
&& (_secondaryItems != null)
&& (_secondaryItems.Count > 0))
{
_table = OpenDropDownMenu(Element as IAddToolbarItem);
Add(_table);
}
}
else
CloseDropDownMenu();
}
private bool DoesTableExist()
{
if (View?.Subviews != null)
{
foreach (var subview in View.Subviews)
{
if (_table != null && subview == _table)
{
return true;
}
}
}
if (_tapGestureRecognizer != null)
{
_transparentView?.RemoveGestureRecognizer(_tapGestureRecognizer);
_tapGestureRecognizer = null;
}
_table = null;
_tapGestureRecognizer = null;
return false;
}
private UITableView OpenDropDownMenu(IAddToolbarItem secondaryMenuSupport)
{
_transparentView = _transparentView = new UIView(new CGRect(0, 0, View.Bounds.Width, View.Bounds.Height))
{
BackgroundColor = UIColor.FromRGBA(0, 0, 0, 0)
};
_tapGestureRecognizer = new UITapGestureRecognizer(CloseDropDownMenu);
_transparentView.AddGestureRecognizer(_tapGestureRecognizer);
Add(_transparentView);
UITableView table = null;
if (_secondaryItems != null && _secondaryItems.Count > 0)
{
table = new UITableView(GetPositionForDropDownMenu(secondaryMenuSupport.RowHeight, secondaryMenuSupport.TableWidth))
{
Source = new TableSource(_secondaryItems, _transparentView),
ClipsToBounds = false
};
table.ScrollEnabled = true;
table.Layer.ShadowColor = secondaryMenuSupport.ShadowColor.ToCGColor();
table.Layer.ShadowOpacity = secondaryMenuSupport.ShadowOpacity;
table.Layer.ShadowRadius = secondaryMenuSupport.ShadowRadius;
table.Layer.ShadowOffset = new SizeF(secondaryMenuSupport.ShadowOffsetDimension, secondaryMenuSupport.ShadowOffsetDimension);
table.BackgroundColor = secondaryMenuSupport.MenuBackgroundColor.ToUIColor();
}
return table;
}
public override void ViewWillDisappear(bool animated)
{
CloseDropDownMenu();
base.ViewWillDisappear(animated);
}
private RectangleF GetPositionForDropDownMenu(float rowHeight, float tableWidth)
{
if ((View?.Bounds != null)
&& (_secondaryItems != null)
&& (_secondaryItems.Count > 0))
{
return new RectangleF(
(float)View.Bounds.Width - tableWidth,
0,
tableWidth,
_secondaryItems.Count() * rowHeight);
}
else
{
return new RectangleF(0.0f, 0.0f, 0.0f, 0.0f);
}
}
private void CloseDropDownMenu()
{
if (_table != null)
{
if (_tapGestureRecognizer != null)
{
_transparentView?.RemoveGestureRecognizer(_tapGestureRecognizer);
_tapGestureRecognizer = null;
}
if (View?.Subviews != null)
{
foreach (var subview in View.Subviews)
{
if (subview == _table)
{
_table.RemoveFromSuperview();
break;
}
}
if (_transparentView != null)
{
foreach (var subview in View.Subviews)
{
if (subview == _transparentView)
{
_transparentView.RemoveFromSuperview();
break;
}
}
}
}
_table = null;
_transparentView = null;
}
}
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
if (_table != null)
{
if (Element is IAddToolbarItem secondaryMenuSupport)
PositionExistingDropDownMenu(secondaryMenuSupport.RowHeight, secondaryMenuSupport.TableWidth);
}
}
private void PositionExistingDropDownMenu(float rowHeight, float tableWidth)
{
if ((View?.Bounds != null)
&& (_secondaryItems != null)
&& (_secondaryItems.Count > 0)
&& (_table != null))
{
_table.Frame = GetPositionForDropDownMenu(rowHeight, tableWidth);
}
}
}
}
ADDITION:
public class TableSource : UITableViewSource
{
List<ToolbarItem> _tableItems;
string[] _tableItemTexts;
string CellIdentifier = "TableCell";
UIView _tableSuperView = null;
public TableSource(List<ToolbarItem> items, UIView tableSuperView)
{
_tableItems = items;
_tableSuperView = tableSuperView;
_tableItemTexts = items.Select(a => a.Text).ToArray();
}
public override nint RowsInSection(UITableView tableview, nint section)
{
return _tableItemTexts?.Length ?? 0;
}
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
UITableViewCell cell = tableView.DequeueReusableCell(CellIdentifier);
string item = _tableItemTexts[indexPath.Row];
if (cell == null)
{ cell = new UITableViewCell(UITableViewCellStyle.Default, CellIdentifier); }
cell.TextLabel.Text = item;
return cell;
}
public override nfloat GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
{
return 56;
}
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
var command = _tableItems[indexPath.Row].Command;
command.Execute(_tableItems[indexPath.Row].CommandParameter);
tableView.DeselectRow(indexPath, true);
tableView.RemoveFromSuperview();
if (_tableSuperView != null)
{
_tableSuperView.RemoveFromSuperview();
}
}
}
public interface IAddToolbarItem
{
event EventHandler ToolbarItemAdded;
Color CellBackgroundColor { get; }
Color CellTextColor { get; }
Color MenuBackgroundColor { get; }
float RowHeight { get; }
Color ShadowColor { get; }
float ShadowOpacity { get; }
float ShadowRadius { get; }
float ShadowOffsetDimension { get; }
float TableWidth { get; }
}
Here you can download the project that reproduces the issue:
https://github.com/DavidShochet/Public
Well, I still can't reproduce the problem with your updated code. I don't if it is a solution and I just want to clarify my comment here.
In your code, you add the _table to the View:
_table = OpenDropDownMenu(Element as IAddToolbarItem);
Add(_table);
What I want you to have a try is add the _table to _transparentView :
_table = OpenDropDownMenu(Element as IAddToolbarItem);
//Add(_table);
_transparentView.Add(_table);
It would be better if you can provide us a Minimal, Reproducible Example so that I can debug it on my side.
Update:
I found the problem is here, you set the height of table _secondaryItems.Count() * rowHeight) which is longer then the view when the toolbar menu grew longer:
private RectangleF GetPositionForDropDownMenu(float rowHeight, float tableWidth)
{
if ((View?.Bounds != null)
&& (_secondaryItems != null)
&& (_secondaryItems.Count > 0))
{
return new RectangleF(
(float)View.Bounds.Width - tableWidth,
0,
tableWidth,
//here is the cause
_secondaryItems.Count() * rowHeight);
}
else
{
return new RectangleF(0.0f, 0.0f, 0.0f, 0.0f);
}
}
Solution: change the height of tablview to (float)View.Bounds.Height:
private RectangleF GetPositionForDropDownMenu(float rowHeight, float tableWidth)
{
if ((View?.Bounds != null)
&& (_secondaryItems != null)
&& (_secondaryItems.Count > 0))
{
return new RectangleF(
(float)View.Bounds.Width - tableWidth,
0,
tableWidth,
(float)View.Bounds.Height);
}
else
{
return new RectangleF(0.0f, 0.0f, 0.0f, 0.0f);
}
}

AutoComplete javaFx ComboBox items not showing after "clearing selection"

I'm trying to make an autocomplete combobox in javafx and im almost done but every time i try to submit and clear selection so the user can choose another choice , all the items get hidden except the one he chose last time
// this the class that i used turn a normal combobox to an autocomplete one
public class AutoCompleteComboBoxListener<T> implements EventHandler<KeyEvent> {
private ComboBox<T> comboBox;
private ObservableList<T> data;
private boolean moveCaretToPos = false;
private int caretPos;
public AutoCompleteComboBoxListener(final ComboBox<T> comboBox) {
this.comboBox = comboBox;
data = comboBox.getItems();
this.comboBox.setEditable(true);
this.comboBox.setOnKeyReleased(AutoCompleteComboBoxListener.this);
this.showOnFocus();
}
#Override
public void handle(KeyEvent event) {
if(event.getCode() == KeyCode.UP) {
caretPos = -1;
moveCaret(comboBox.getEditor().getText().length());
return;
} else if(event.getCode() == KeyCode.DOWN) {
if(!comboBox.isShowing())
comboBox.show();
caretPos = -1;
moveCaret(comboBox.getEditor().getText().length());
return;
}
if (event.getCode() == KeyCode.RIGHT || event.getCode() == KeyCode.LEFT
|| event.isControlDown() || event.getCode() == KeyCode.HOME
|| event.getCode() == KeyCode.END || event.getCode() == KeyCode.TAB) {
return;
}
System.out.println(caretPos);
comboBox.hide();
if(event.getCode() == KeyCode.BACK_SPACE) {
moveCaretToPos = true;
caretPos = comboBox.getEditor().getCaretPosition();
} else if(event.getCode() == KeyCode.DELETE) {
moveCaretToPos = true;
caretPos = comboBox.getEditor().getCaretPosition();
}
ObservableList<T> list = FXCollections.observableArrayList();
for (int i=0; i<data.size(); i++) {
if(data.get(i).toString().toLowerCase().startsWith(
AutoCompleteComboBoxListener.this.comboBox
.getEditor().getText().toLowerCase())) {
list.add(data.get(i));
}
}
String t = comboBox.getEditor().getText();
comboBox.setItems(list);
comboBox.getEditor().setText(t);
if(!moveCaretToPos) {
caretPos = -1;
}
moveCaret(t.length());
if(!list.isEmpty()) {
comboBox.show();
String editorText;
editorText=comboBox.getEditor().getText();
list.sort((a, b) -> Integer.compare(a.toString().length(), b.toString().length()));
comboBox.getSelectionModel().selectFirst();
comboBox.getEditor().setText(editorText);
moveCaret(comboBox.getSelectionModel().getSelectedItem().toString().length());
}
if (event.getCode()==KeyCode.ENTER){
comboBox.hide();
comboBox.getEditor().setText(comboBox.getSelectionModel().getSelectedItem().toString());
}
}
private void moveCaret(int textLength) {
if(caretPos == -1)
comboBox.getEditor().positionCaret(textLength);
else
comboBox.getEditor().positionCaret(caretPos);
moveCaretToPos = false;
}
public void showOnFocus(){
this.comboBox.focusedProperty().addListener((obs, oldVal, newVal) ->
this.comboBox.show());
}
}
// and this is what the submit button does the the comboboxes
button.setOnAction(e-> {
combo1.getSelectionModel().clearSelection();
combo1.getSelectionModel().clearSelection();
});

JavaFX TimeTextField

Base Article:
[https://community.oracle.com/thread/2552039?start=0&tstart=0][1]
I extend the solution from the oracle community with the event filters in the constructor for a better user ergonomics.
The JavaFX TimeTextField provides all functions needed for time handling.
Unfortunately it's missing in the JavaFX Standard in Java8, where a time handling field is a must in my point of view as in other technologies it is possible.
The thread in the oracle community is not as frequent as I expect it, please test it and try to improve it! We need it...
Features:
- only numeric keys are accepted.
- validation for a given time format
- (my improvement) when the last number of a block like hours or minutes was reached, the cursor jumps to the next block.
package test;
import java.util.regex.Pattern;
import javafx.beans.binding.Bindings;
import javafx.application.Application;
import javafx.beans.binding.IntegerBinding;
import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.ReadOnlyIntegerWrapper;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.IndexRange;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TimeTextFieldTest extends Application {
#Override
public void start(Stage primaryStage) {
VBox root = new VBox(5);
root.setPadding(new javafx.geometry.Insets(5));
Label hrLabel = new Label();
Label minLabel = new Label();
Label secLabel = new Label();
TimeTextField timeTextField = new TimeTextField();
hrLabel.textProperty().bind(Bindings.format("Hours: %d", timeTextField.hoursProperty()));
minLabel.textProperty().bind(Bindings.format("Minutes: %d", timeTextField.minutesProperty()));
secLabel.textProperty().bind(Bindings.format("Seconds: %d", timeTextField.secondsProperty()));
root.getChildren().addAll(timeTextField, hrLabel, minLabel, secLabel);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
public static class TimeTextField extends TextField {
enum Unit {
HOURS, MINUTES, SECONDS
};
private final Pattern timePattern;
private final ReadOnlyIntegerWrapper hours;
private final ReadOnlyIntegerWrapper minutes;
private final ReadOnlyIntegerWrapper seconds;
public TimeTextField() {
this("00:00:00");
this.addEventFilter(KeyEvent.KEY_TYPED, new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent inputevent) {
int c = TimeTextField.this.getCaretPosition();
if (c <= 7) {
if (!"1234567890:".contains(inputevent.getCharacter().toLowerCase())) {
inputevent.consume();
}
} else {
inputevent.consume();
}
}
});
this.addEventFilter(KeyEvent.KEY_RELEASED, new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent inputevent) {
boolean withMinutes = false;
if (TimeTextField.this.getText() != null && TimeTextField.this.getText().length() >= 5
&& TimeTextField.this.getText().indexOf(":") == 2) {
withMinutes = true;
}
boolean withSeconds = false;
if (TimeTextField.this.getText() != null && TimeTextField.this.getText().length() == 8
&& TimeTextField.this.getText().lastIndexOf(":") == 5) {
withSeconds = true;
}
int c = TimeTextField.this.getCaretPosition();
if (((c == 2 && withMinutes) || (c == 5 && withSeconds))
&& (inputevent.getCode() != KeyCode.LEFT && inputevent.getCode() != KeyCode.BACK_SPACE)) {
TimeTextField.this.forward();
inputevent.consume();
}
}
});
}
public TimeTextField(String time) {
super(time);
// timePattern = Pattern.compile("\\d\\d:\\d\\d:\\d\\d");
timePattern = Pattern.compile("([01]?[0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]");
if (!validate(time)) {
throw new IllegalArgumentException("Invalid time: " + time);
}
hours = new ReadOnlyIntegerWrapper(this, "hours");
minutes = new ReadOnlyIntegerWrapper(this, "minutes");
seconds = new ReadOnlyIntegerWrapper(this, "seconds");
hours.bind(new TimeTextField.TimeUnitBinding(Unit.HOURS));
minutes.bind(new TimeTextField.TimeUnitBinding(Unit.MINUTES));
seconds.bind(new TimeTextField.TimeUnitBinding(Unit.SECONDS));
}
public ReadOnlyIntegerProperty hoursProperty() {
return hours.getReadOnlyProperty();
}
public int getHours() {
return hours.get();
}
public ReadOnlyIntegerProperty minutesProperty() {
return minutes.getReadOnlyProperty();
}
public int getMinutes() {
return minutes.get();
}
public ReadOnlyIntegerProperty secondsProperty() {
return seconds.getReadOnlyProperty();
}
public int getSeconds() {
return seconds.get();
}
#Override
public void appendText(String text) {
// Ignore this. Our text is always 8 characters long, we cannot
// append anything
}
#Override
public boolean deleteNextChar() {
boolean success = false;
// If there's a selection, delete it:
final IndexRange selection = getSelection();
if (selection.getLength() > 0) {
int selectionEnd = selection.getEnd();
this.deleteText(selection);
this.positionCaret(selectionEnd);
success = true;
} else {
// If the caret preceeds a digit, replace that digit with a zero
// and move the caret forward. Else just move the caret forward.
int caret = this.getCaretPosition();
if (caret % 3 != 2) { // not preceeding a colon
String currentText = this.getText();
setText(currentText.substring(0, caret) + "0" + currentText.substring(caret + 1));
success = true;
}
this.positionCaret(Math.min(caret + 1, this.getText().length()));
}
return success;
}
#Override
public boolean deletePreviousChar() {
boolean success = false;
// If there's a selection, delete it:
final IndexRange selection = getSelection();
if (selection.getLength() > 0) {
int selectionStart = selection.getStart();
this.deleteText(selection);
this.positionCaret(selectionStart);
success = true;
} else {
// If the caret is after a digit, replace that digit with a zero
// and move the caret backward. Else just move the caret back.
int caret = this.getCaretPosition();
if (caret % 3 != 0) { // not following a colon
String currentText = this.getText();
setText(currentText.substring(0, caret - 1) + "0" + currentText.substring(caret));
success = true;
}
this.positionCaret(Math.max(caret - 1, 0));
}
return success;
}
#Override
public void deleteText(IndexRange range) {
this.deleteText(range.getStart(), range.getEnd());
}
#Override
public void deleteText(int begin, int end) {
// Replace all digits in the given range with zero:
StringBuilder builder = new StringBuilder(this.getText());
for (int c = begin; c < end; c++) {
if (c % 3 != 2) { // Not at a colon:
builder.replace(c, c + 1, "0");
}
}
this.setText(builder.toString());
}
#Override
public void insertText(int index, String text) {
// Handle an insert by replacing the range from index to
// index+text.length() with text, if that results in a valid string:
StringBuilder builder = new StringBuilder(this.getText());
builder.replace(index, index + text.length(), text);
final String testText = builder.toString();
if (validate(testText)) {
this.setText(testText);
}
this.positionCaret(index + text.length());
}
#Override
public void replaceSelection(String replacement) {
final IndexRange selection = this.getSelection();
if (selection.getLength() == 0) {
this.insertText(selection.getStart(), replacement);
} else {
this.replaceText(selection.getStart(), selection.getEnd(), replacement);
}
}
#Override
public void replaceText(IndexRange range, String text) {
this.replaceText(range.getStart(), range.getEnd(), text);
}
#Override
public void replaceText(int begin, int end, String text) {
if (begin == end) {
this.insertText(begin, text);
} else {
// only handle this if text.length() is equal to the number of
// characters being replaced, and if the replacement results in
// a valid string:
if (text.length() == end - begin) {
StringBuilder builder = new StringBuilder(this.getText());
builder.replace(begin, end, text);
String testText = builder.toString();
if (validate(testText)) {
this.setText(testText);
}
this.positionCaret(end);
}
}
}
private boolean validate(String time) {
if (!timePattern.matcher(time).matches()) {
return false;
}
String[] tokens = time.split(":");
assert tokens.length == 3;
try {
int hours = Integer.parseInt(tokens[0]);
int mins = Integer.parseInt(tokens[1]);
int secs = Integer.parseInt(tokens[2]);
if (hours < 0 || hours > 23) {
return false;
}
if (mins < 0 || mins > 59) {
return false;
}
if (secs < 0 || secs > 59) {
return false;
}
return true;
} catch (NumberFormatException nfe) {
// regex matching should assure we never reach this catch block
assert false;
return false;
}
}
private final class TimeUnitBinding extends IntegerBinding {
final Unit unit;
TimeUnitBinding(Unit unit) {
this.bind(textProperty());
this.unit = unit;
}
#Override
protected int computeValue() {
// Crazy enum magic
String token = getText().split(":")[unit.ordinal()];
return Integer.parseInt(token);
}
}
}
}

DevExpress GridControl Does not Update properly even I set up the NotifyPropertyChanged event correctly

I met a very strange Problem.
The basic idea is that I have a class to save data received from a trading api about forex price. Each property has been set with NotifyPropertyChanged method like below.
class RealTimeBar
{
public event PropertyChangedEventHandler PropertyChanged;
private const double EPSILON = 0.0000001;
private int _id;
private string _symbol;
private int _time;
private float _open;
private float _high;
private float _low;
private float _close;
int _volume;
public RealTimeBar(int id, string symbol)
{
_id = id;
_symbol = symbol;
}
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public int Id
{
get
{
return _id;
}
set
{
_id = value;
}
}
public string Symbol
{
get
{
return _symbol;
}
set
{
if (value != _symbol)
{
_symbol = value;
NotifyPropertyChanged("Symbol");
}
}
}
public int Time
{
get
{
return _time;
}
set
{
if (value != _time)
{
_time = value;
NotifyPropertyChanged("Time");
}
}
}
public float Open
{
get
{
return _open;
}
set
{
if (value != _open)
{
_open = value;
NotifyPropertyChanged("Open");
}
}
}
public float High
{
get
{
return _high;
}
set
{
if (value != _high)
{
_high = value;
NotifyPropertyChanged("High");
}
}
}
public float Low
{
get
{
return _low;
}
set
{
if (value != _low)
{
_low = value;
NotifyPropertyChanged("Low");
}
}
}
public float Close
{
get
{
return _close;
}
set
{
if (value != _close)
{
_close = value;
NotifyPropertyChanged("Close");
}
}
}
public int Volume
{
get
{
return _volume;
}
set
{
if (value != _volume)
{
_volume = value;
NotifyPropertyChanged("Volume");
}
}
}
}
It is quote a long class but with simple structure as you can see. Now I connected to api which fire event to me and I handle it by set the value from api to the class i defined.
BindingList<RealTimeBar> _realTimeBarList = new BindingList<RealTimeBar>();
public Hashtable _iForexHashtable = new Hashtable();
private void _UpdateForexQuote(int tickerId, int time, double open, double high, double low, double close, int volume,
double wap, int count)
{
///MessageBox.Show(tickerId.ToString());
((RealTimeBar)_iForexHashtable[tickerId]).Open = (float)open;
((RealTimeBar)_iForexHashtable[tickerId]).High = (float)high;
((RealTimeBar)_iForexHashtable[tickerId]).Low = (float)low;
((RealTimeBar)_iForexHashtable[tickerId]).Close = (float)close;
((RealTimeBar)_iForexHashtable[tickerId]).Volume = volume;
}
After some setting up, the method _UpdateForexQuote would distribute the coming info into properties of RealTimeBar class. Everything is fine.
When I start the program, it does not update. I thought that there is no data coming in. But when I randomly click somewhere in the A1cell of gridcontrol, then click another B1cell, the previous A1cell would update. Then if i click C1cell, then the B1cell would update. If you do not click one cell , it would never update. I show you the picture:
As you can see, that after clicking first three lines, the first three lines showed delayed data and since I never touch the fourth line, it shows zero. And the condition is that I just clicked the fifth line Low cell, that is why the Low does not update but other cells updated. It is very strange. I use same code before under devexpress 11 with vs 2010. But now with devexpress 12 with vs 2012, I met this problem which never occurred before.
UPDATE:
Below is the method I use to 1. define bindinglist and a hashtable, 2. put objects into the hashtable first and add the object from hashtable to bindinglist 3. bind the bindinglist to gridcontrol.
private void earningButtonItem_ItemClick(object sender, ItemClickEventArgs e)
{
_iTimer.AutoReset = false;
_iTimer.Enabled = false;
switchStockPool = "Earning Stock";
disconnectButtonItem.PerformClick();
connectButtonItem.PerformClick();
_iheitanshaoEarningDBConnect = new DBConnect("heitanshaoearning");
List<string>[] tempList;
int tempHash;
tempList = _iheitanshaoEarningDBConnect.SelectSymbolHighLow();
_quoteEarningOnGridList.Clear();
///tempList[0].Count
for (int i = 0; i < tempList[0].Count; i++)
{
tempHash = Convert.ToInt32(tempList[0][i].ToString().GetHashCode());
_iStockEarningHistHashtable[tempHash] = new QuoteOnGridHist(tempList[0][i], (float)Convert.ToSingle(tempList[1][i]), (float)Convert.ToSingle(tempList[2][i]), (float)Convert.ToSingle(tempList[3][i]));
_iStockEarningHashtable[tempHash] = new QuoteOnGrid(tempList[0][i], 0, 0);
_quoteEarningOnGridList.Add((QuoteOnGrid)_iStockEarningHashtable[tempHash]);
reqMktDataExStock(tempHash, tempList[0][i].ToString());
}
List<string>[] tempVolumeList;
tempVolumeList = _iheitanshaoEarningDBConnect.SelectAverageVolume();
for (int i = 0; i < tempList[0].Count; i++)
{
tempHash = Convert.ToInt32(tempVolumeList[0][i].ToString().GetHashCode());
((QuoteOnGrid)_iStockEarningHashtable[tempHash]).Average_Volume = ((float)Convert.ToSingle(tempVolumeList[1][i])) / volumeDenominator;
}
gridControl.DataSource = _quoteEarningOnGridList;
}
/////////////////////
Now when the price update event comes, the method below will update the object properties in hashtable. Since I defined Notifypropertychanged in object, it should update the object in bingdinglist and gridcontrol.
private void _UpdateStockMarketQuote(int tikcerId, int field, double price, int canAutoExecute)
{
////MessageBox.Show(tikcerId.ToString() + field.ToString() + price.ToString());
if (switchStockPool == "Selected Stock")
{
if (field == 4)
{
((QuoteOnGrid)_iStockHashtable[tikcerId]).Gap_From_High = ((float)price - ((QuoteOnGridHist)_iStockHistHashtable[tikcerId]).High) / ((QuoteOnGridHist)_iStockHistHashtable[tikcerId]).Close;
((QuoteOnGrid)_iStockHashtable[tikcerId]).Gap_From_Low = ((float)price - ((QuoteOnGridHist)_iStockHistHashtable[tikcerId]).Low) / ((QuoteOnGridHist)_iStockHistHashtable[tikcerId]).Close;
((QuoteOnGrid)_iStockHashtable[tikcerId]).Last_Price = (float)price;
}
//else if (field == 1)
//{
// ((QuoteOnGrid)_iStockHashtable[tikcerId]).Gap_From_High = ((float)price - ((QuoteOnGridHist)_iStockHistHashtable[tikcerId]).High) / ((QuoteOnGridHist)_iStockHistHashtable[tikcerId]).Close;
// ((QuoteOnGrid)_iStockHashtable[tikcerId]).Gap_From_Low = ((float)price - ((QuoteOnGridHist)_iStockHistHashtable[tikcerId]).Low) / ((QuoteOnGridHist)_iStockHistHashtable[tikcerId]).Close;
//}
}
else if (switchStockPool == "Earning Stock")
{
if (field == 4)
{
((QuoteOnGrid)_iStockEarningHashtable[tikcerId]).Gap_From_High = ((float)price - ((QuoteOnGridHist)_iStockEarningHistHashtable[tikcerId]).High) / ((QuoteOnGridHist)_iStockEarningHistHashtable[tikcerId]).Close;
((QuoteOnGrid)_iStockEarningHashtable[tikcerId]).Gap_From_Low = ((float)price - ((QuoteOnGridHist)_iStockEarningHistHashtable[tikcerId]).Low) / ((QuoteOnGridHist)_iStockEarningHistHashtable[tikcerId]).Close;
((QuoteOnGrid)_iStockEarningHashtable[tikcerId]).Last_Price = (float)price;
}
//else if (field == 1)
//{
// ((quoteongrid)_istockearninghashtable[tikcerid]).gap_from_high = ((float)price - ((quoteongridhist)_istockearninghisthashtable[tikcerid]).high) / ((quoteongridhist)_istockearninghisthashtable[tikcerid]).close;
// ((quoteongrid)_istockearninghashtable[tikcerid]).gap_from_low = ((float)price - ((quoteongridhist)_istockearninghisthashtable[tikcerid]).low) / ((quoteongridhist)_istockearninghisthashtable[tikcerid]).close;
//}
}
}
Not only you need to have PropertyChanged event in a class, you need to implement INotifyPropertyChanged. That's how the grid knows a class can inform of changes.

Resources