When hovering over a marker using GMap.NET, it does not fire the events:
private void gMapControl1_OnMarkerEnter(GMapMarker item)
{
currentMarker = item;
}
private void gMapControl1_OnMarkerLeave(GMapMarker item)
{
currentMarker = null;
}
Here is the code that creates the marker:
// Add marker
currentMarker = new GMarkerGoogle(new PointLatLng(y, x), GMarkerGoogleType.yellow_small);
currentMarker.IsHitTestVisible = false;
currentMarker.Tag = iCurrentPolygon + "." + iCurrentPolygonPointIndex;
top.Markers.Add(currentMarker);
I can add markers but when I hover over them it does not execute the onMarkEnter/onMarkLeave
I guess you need to set IsHitTestVisible to true in order for the marker to receive event notifications.
Related
As the title suggests, my button (closeImage) goes null when I try to access it.
I have a canvas that pops up an image based on a tapped marker (button component) on a map. The said canvas will show the image together with the button in question. Clicking on the button closes the pop-up;
The button in question
However, once I press on the marker on the map to show the pop-up image, it gives me an error that the Close image is null. The close button does show on the first time I press on the marker on the map and able to close the pop-up, even with the error message. But once I click on the same marker on the map again, the image shows but the close button is not showing anymore.
Here's my block of code:
public class TappedMarkerManager : MonoBehaviour {
private static TappedMarkerManager instance;
private List<GameObject> markerImageList = new List<GameObject>();
string tappedMarkerName;
Button closeImage;
GameObject closeImageGO;
public static TappedMarkerManager Instance {
get {
if (instance == null) {
instance = FindObjectOfType<TappedMarkerManager>();
}
return instance;
}
}
void Start() {
closeImageGO = GameObject.FindGameObjectWithTag("close_img");
if(closeImageGO != null) {
closeImage = closeImageGO.GetComponent<Button>();
closeImage.onClick.AddListener(TappedMarkerInformation);
} else {
Debug.Log("closeImageGO is null");
}
//Loop through preview images.
//This will dynamically add the images on the list if more markers will be added on the map
//Use tag "marker_img"; must be same name with the marker and image
var markerImages = GameObject.FindGameObjectsWithTag("marker_img");
foreach(var markerImage in markerImages) {
TappedMarkerManager.Instance.markerImageList.Add(markerImage.gameObject);
//Set false to hide it at first
markerImage.SetActive(false);
}
}
public void TappedMarkerInformation() {
var name = tappedMarkerName;
foreach(var img in TappedMarkerManager.Instance.markerImageList) {
if(name == img.GetComponent<Image>().name) {
img.SetActive(true);
if(closeImage != null) {
if(!closeImage.gameObject.activeInHierarchy) {
closeImage.gameObject.SetActive(true);
}
} else {
Debug.LogError("Close image is null");
}
}
if(GetButtonName() == "ExitBtn") {
img.SetActive(false);
closeImage.gameObject.SetActive(false);
}
}
}
public void SetTappedName(string name) {
tappedMarkerName = name;
TappedMarkerInformation();
}
//Get the name of the button pressed
public string GetButtonName() {
if(EventSystem.current.currentSelectedGameObject != null) {
string ClickedButtonName = EventSystem.current.currentSelectedGameObject.name;
return ClickedButtonName;
} else {
return "";
}
}
}
The markers on the map are controlled from another script with:
...
TappedMarkerManager tappedMarkerManager;
tappedMarkerManager = new TappedMarkerManager();
...
private void HandleFingerTap(LeanFinger finger) {
Ray ray = Camera.main.ScreenPointToRay(finger.ScreenPosition);
if (Physics.Raycast(ray, out RaycastHit hit)) {
string markerName = hit.collider.gameObject.name;
tappedMarkerManager.SetTappedName(markerName);
}
}
Any help is highly appreciated. Thank you. I can provide the github repository if needed.
What I tried so far:
I already tried setting up the closeImage in the Inspector with the button
Made sure that the GameObject has the button component
Checked and verified on the hierarchy that the closeImage button is not being destroyed
I have two buttons set up in an interactive fiction game, each press calls a new line of text. The problem is, every time I press on the button, I get two logged debug messages informing me of the click and my game moves two sections of text.
I've tried many different things to try to work around this including trying to alter the submit input in project settings and many different code forms. Any help would be greatly appreciated.
Here's my current code:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class AdventureGame : MonoBehaviour
{
[SerializeField]
private Text _textComponent;
[SerializeField]
private State _startingState;
private State state;
[SerializeField]
private Button _input0Button;
[SerializeField]
private Button _input1Button;
[SerializeField]
private Text _choices1;
[SerializeField]
private Text _choices2;
private bool _buttonOnePressed;
private bool _buttonTwoPressed;
void Start()
{
state = _startingState;
_textComponent.text = state.GetStateStory();
_input0Button.onClick.AddListener(Input0Button);
_input1Button.onClick.AddListener(Input1Button);
_buttonOnePressed = false;
_buttonTwoPressed = false;
}
void Update()
{
ManageState();
}
private void ManageState()
{
if (state._choice == true)
{
_choices1.text = state.GetChoiceOne();
_choices2.text = state.GetChoiceTwo();
_textComponent.text = state.GetStateStory();
_input0Button.gameObject.SetActive(true);
_input1Button.gameObject.SetActive(true);
if(_buttonOnePressed == true)
{
StartCoroutine(WaitForItOne());
}
else if(_buttonTwoPressed == true)
{
StartCoroutine(WaitForItTwo());
}
}
else if (state._choice == false)
{
_choices1.text = state.GetChoiceOne();
_choices2.text = state.GetChoiceTwo();
_textComponent.text = state.GetStateStory();
_input0Button.gameObject.SetActive(true);
_input1Button.gameObject.SetActive(false);
if(_buttonOnePressed == true)
{
StartCoroutine(WaitForItOne());
}
}
}
private void ManageChoiceOne()
{
_buttonOnePressed = false;
State[] _newState = state.GetNextStatesArray();
state = _newState[0];
}
private void ManageChoiceTwo()
{
_buttonTwoPressed = false;
State[] _newState = state.GetNextStatesArray();
state = _newState[1];
}
public void Input0Button()
{
Debug.Log("Input 0 pressed");
_buttonOnePressed = true;
}
public void Input1Button()
{
Debug.Log("Input 1 pressed");
_buttonTwoPressed = true;
}
IEnumerator WaitForItOne()
{
yield return new WaitForSeconds(3.0f);
ManageChoiceOne();
}
IEnumerator WaitForItTwo()
{
yield return new WaitForSeconds(3.0f);
ManageChoiceTwo();
}
}
First of all you keep starting new Coroutines each frame as long as e.g. _buttonOnePressed == true .. you wait 3 seconds before you finally unset this flag!
Then for the double call make sure the callbacks are not configured also in the Inspector! It seems like you have them once in the Inspector and additionally add them in your Start method so they are called twice!
Note that you won't see the callbacks added on runtime in the Inspector!
Why are you even using Update here at all? It is quite redundant to poll the state and the bool values and constantly check and handle their states in each frame. I would rather simply start the routine in the button method itself and make the whole code event driven instead!
(optionally) To give the user better feedback I would additionally in the meantime during the 3 seconds make the buttons non-interactable .. keep them active but not clickable:
// Remove state;
// Remove _buttonOnePressed
// Remove _buttonTwoPressed
private void Start()
{
// Either remove this two lines or the callbacks set in the Inspector
_input0Button.onClick.AddListener(Input0Button);
_input1Button.onClick.AddListener(Input1Button);
ManageState(_startingState);
}
// Remove Update
// This will be called only when actually needed
// since the state is passed in as parameter you don't need the private field
private void ManageState(State state)
{
// These happen in both cases anyway
_choices1.text = state.GetChoiceOne();
_choices2.text = state.GetChoiceTwo();
_textComponent.text = state.GetStateStory();
_input0Button.gameObject.SetActive(true);
// Here directly use the state flag
// since the button is disabled when needed
// there is no need for having different "states"
// since anyway only the according button(s) is(are) available
_input1Button.gameObject.SetActive(state._choice);
}
// (optional) Flag for avoiding concurrent routines
// Should be impossible since buttons get disabled but you never know
private bool alreadyHandlingButton;
private IEnumerator ManageChoice(bool isButtonOne)
{
// (optional) Skip if another routine running
if(alreadyHandlingButton) yield break;
// (optional) Block other routines just in cade
alreadyHandlingButton = true;
// Disable interactions
_input0Button.interactable = false;
_input1Button.interactable = false;
// This is the same for both buttons
yield return new WaitForSeconds(3f);
State[] _newState = state.GetNextStatesArray();
var state = _newState[isButtonOne ? 0 : 1];
// Only call ManageState when the state is actually changing
ManageState(state);
// (optional) Allow a new routine
alreadyHandlingButton = false;
// Enable interactions
_input0Button.interactable = true;
_input1Button.interactable = true;
}
public void Input0Button()
{
// (optional) Ignore if other button is already handled
if(alreadyHandlingButton) return;
Debug.Log("Input 0 pressed");
StartCoroutine(ManageChoice(true));
}
public void Input1Button()
{
// (optional) Ignore if other button is already handled
if(alreadyHandlingButton) return;
Debug.Log("Input 1 pressed");
StartCoroutine(ManageChoice(false));
}
While trying to add marker to mapbox using symbol layer, the marker was not visible.
Tried to add marker as shown below,
mapView.getMapAsync(m -> {
isMapReady = true;
mapboxMap = m;
mapboxMap.addOnMapClickListener(MapViewFragment.this);
addMarkerToSymbolLayer(45);
updateCameraPosition(location); });
private void updateCameraPosition(Location location){
if (mapboxMap != null) {
LatLng latLong = new LatLng();
if (location != null) {
latLong.setLatitude(location.getLatitude());
latLong.setLongitude(location.getLongitude());
}
final CameraPosition cameraPosition = new CameraPosition.Builder()
.target(latLong)
.build();
mapboxMap.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
mapView.postDelayed(() -> mapboxMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition), AppConstants.MAP_CAMERA_ANIMATE_DURATION_MS_5000), 100);
private void addMarkerToSymbolLayer(float headDirection) {
GeoJsonSource geoJsonSource = new GeoJsonSource("geojson-source",
Feature.fromGeometry(Point.fromLngLat(77.6387, 12.9610)));
mapboxMap.addSource(geoJsonSource);
Bitmap compassNeedleSymbolLayerIcon = BitmapFactory.decodeResource(
getResources(), R.drawable.compass_needle);
mapboxMap.addImage("compass-needle-image-id", compassNeedleSymbolLayerIcon);
SymbolLayer aircraftLayer = new SymbolLayer("aircraft-layer", "geojson-source")
.withProperties(
PropertyFactory.iconImage("compass-needle-image-id"),
PropertyFactory.iconRotate(headDirection),
PropertyFactory.iconIgnorePlacement(true),
PropertyFactory.iconAllowOverlap(true)
);
mapboxMap.addLayer(aircraftLayer);
}
If addMarkerToSymbolLayer() is called after moving camera then the marker is visibile. Why is that adding marker dependent on camera position? I have to move camera many times to meet my requirement. How to handle this?
Also in the Deprecated MarkerView and MarkerViewOptions I didnt have any issues while adding marker.
I noticed that if given a delay of 100ms while calling the function addMarkerToSymbolLayer(45), the marker is visible and things work fine !
The onMapReady() is called even before all the styles have been loaded. It is necessary that all styles be loaded before adding a symbol layer. Add the following listener and call for adding marker inside it.
mapView.addOnDidFinishLoadingStyleListener(new MapView.OnDidFinishLoadingStyleListener() {
#Override
public void onDidFinishLoadingStyle() {
}
});
I have a windows form which is launched with an ESRI AddIn button (ArcGIS 10.2 and Windows 7). On my form I have a button to pick a point from the Map. I have added an ESRI BaseTool class to the project, which has an OnMouseDown event.
The problem is that I cannot get the Tool to run. Note that the tool is not on the ArcGIS Command Bar (like the button is) but the tool is still found by the Find(uid) process.
When the Tool was added to the project (using the ArcGIS Add BaseTool process) it didn't update the .esriaddinx file. I had to do that manually.
My Addin file is:
<AddIn language="CLR4.0" library="HVLR_Processing.dll" namespace="HVLR_Processing">
<ArcMap>
<Commands>
<Button id="RMS_HVLR_Processing_clsHVLR_Processing" class="clsHVLR_Processing" ...
<Tool id="HVLR_PickTool" class="clsMapPick" category="Add-In Controls" caption="" message="" tip="" image="" />
</Commands>
</ArcMap>
The clsMapClick code contains the OnMouseDown event.
To start the process I have tried many methods. I can retrieve the Tool but when I execute it (or assign it to the CurrentTool) nothing happens.
UID pUID;
ICommandItem pCmdItem;
ICommand pCmd;
clsMapPick pPick;
ITool pTool;
try
{
this.WindowState = FormWindowState.Minimized;
m_pApp.CurrentTool = null;
pUID = new UIDClass();
pUID.Value = "HVLR_PickTool";
pCmdItem = m_pApp.Document.CommandBars.Find(pUID, false, false);
if (pCmdItem != null)
{
m_pApp.CurrentTool = pCmdItem; // Nothing happens
m_pApp.CurrentTool.Execute(); // Nothing happens
m_pApp.CurrentTool.Refresh();
}
}
catch (Exception ex)
Can anyone tell me how to get this tool to execute?
OK. Big stuff-up. You can't add a BaseTool to an ESRI AddIn; it's a COM object. What has to be done is:
Create a new ESRI Tool class.
Add a boolean variable to the class to indicate the mousedown event has fired.
In the OnUpdate method put some code to continue until the mousedown event has fired.
Create an OnMouseDown event handler by starting to type protected void On... and itellisense will allow you to select the event you want to track.
Put the code you want to run in the OnMouseDown event handler and also set the boolean value to true.
Code:
public class clsMapPick : ESRI.ArcGIS.Desktop.AddIns.Tool
{
private bool m_bIsFinished = false;
private int m_iXPixel = -1;
private int m_iYPixel = -1;
//private string m_sError = "";
//private bool m_bSuccess = true;
public clsMapPick()
{
}
protected override void OnActivate()
{
base.OnActivate();
return;
}
protected override void OnUpdate()
{
if (m_bIsFinished)
{
m_bIsFinished = false;
frmHVLR.m_dX = m_iXPixel;
frmHVLR.m_dX = m_iYPixel;
}
}
protected override void OnMouseDown(MouseEventArgs arg)
{
base.OnMouseDown(arg);
m_iXPixel = arg.X;
m_iYPixel = arg.Y;
m_bIsFinished = true;
}
}
In the form where the button for clicking on the map is fired:
string sError = "";
dPickedX = 0;
dPickedY = 0;
UID pUID;
ICommandItem pCmdItem;
ICommandBars pCmdBars;
ICommand pCmd;
ITool pTool;
try
{
this.WindowState = FormWindowState.Minimized;
pCmdBars = m_pApp.Document.CommandBars;
pUID = new UIDClass();
pUID.Value = HVLR_Processing.ThisAddIn.IDs.clsMapPick;
pCmdItem = pCmdBars.Find(pUID);
if (pCmdItem != null)
{
m_pApp.CurrentTool = pCmdItem;
//pCmdItem.Execute();
dPickedX = m_pMxDoc.CurrentLocation.X;
dPickedY = m_pMxDoc.CurrentLocation.Y;
}
return sError;
}
This is working fine for me now, the Tool class is being called but the OnMouseDown event isn't being fired.
If you know why I'd appreciate it.
I am extending the default Menu and MenuItem class to add some animated effects to it.
The problem is that I need to know the width and height of the Menu and MenuItem I'm working on. This classes doesn't extend Node or Region so there are no public methods to get their size. The size is composed by the text size inside the MenuItem plus the corresponding default padding, I can calculate how much space the text takes, but I can't get how much padding the MenuItem has neither.
There is a method called impl_styleableGetNode() that returns a Node but it always returns null for me.
Is there anyway to get the size? MenuBar also doesn't seems to expose any helpful method for this.
EDIT:
Here is my class, I'm trying to implement this material design button into the Menu class. Basically I render all the button using the setGraphic() method. It's working perfectly but I'm using the Pane width which doesn't take into account the padding of the Menu so the effect is not complete.
public class MaterialDesignMenu extends Menu {
private Pane stackPane = new Pane();
private Label label = new Label();
private Circle circleRipple;
private Rectangle rippleClip = new Rectangle();
private Duration rippleDuration = Duration.millis(250);
private double lastRippleHeight = 0;
private double lastRippleWidth = 0;
private Color rippleColor = new Color(1, 0, 0, 0.3);
public MaterialDesignMenu() {
init("");
}
public MaterialDesignMenu(String text) {
init(text);
}
public MaterialDesignMenu(String text, Node graphic) {
init(text);
}
private void init(String text){
label.setText(text);
createRippleEffect();
stackPane.getChildren().addAll(circleRipple, label);
setGraphic(stackPane);
}
private void createRippleEffect() {
circleRipple = new Circle(0.1, rippleColor);
circleRipple.setOpacity(0.0);
// Optional box blur on ripple - smoother ripple effect
//circleRipple.setEffect(new BoxBlur(3, 3, 2));
// Fade effect bit longer to show edges on the end of animation
final FadeTransition fadeTransition = new FadeTransition(rippleDuration, circleRipple);
fadeTransition.setInterpolator(Interpolator.EASE_OUT);
fadeTransition.setFromValue(1.0);
fadeTransition.setToValue(0.0);
final Timeline scaleRippleTimeline = new Timeline();
final SequentialTransition parallelTransition = new SequentialTransition();
parallelTransition.getChildren().addAll(
scaleRippleTimeline,
fadeTransition
);
// When ripple transition is finished then reset circleRipple to starting point
parallelTransition.setOnFinished(event -> {
circleRipple.setOpacity(0.0);
circleRipple.setRadius(0.1);
});
stackPane.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
parallelTransition.stop();
// Manually fire finish event
parallelTransition.getOnFinished().handle(null);
circleRipple.setCenterX(event.getX());
circleRipple.setCenterY(event.getY());
// Recalculate ripple size if size of button from last time was changed
if (stackPane.getWidth() != lastRippleWidth || stackPane.getHeight() != lastRippleHeight) {
lastRippleWidth = stackPane.getWidth();
lastRippleHeight = stackPane.getHeight();
rippleClip.setWidth(lastRippleWidth);
rippleClip.setHeight(lastRippleHeight);
/*
// try block because of possible null of Background, fills ...
try {
rippleClip.setArcHeight(stackPane.getBackground().getFills().get(0).getRadii().getTopLeftHorizontalRadius());
rippleClip.setArcWidth(stackPane.getBackground().getFills().get(0).getRadii().getTopLeftHorizontalRadius());
circleRipple.setClip(rippleClip);
} catch (Exception e) {
e.printStackTrace();
}*/
circleRipple.setClip(rippleClip);
// Getting 45% of longest button's length, because we want edge of ripple effect always visible
double circleRippleRadius = Math.max(stackPane.getHeight(), stackPane.getWidth()) * 0.45;
final KeyValue keyValue = new KeyValue(circleRipple.radiusProperty(), circleRippleRadius, Interpolator.EASE_OUT);
final KeyFrame keyFrame = new KeyFrame(rippleDuration, keyValue);
scaleRippleTimeline.getKeyFrames().clear();
scaleRippleTimeline.getKeyFrames().add(keyFrame);
}
parallelTransition.playFromStart();
});
}
public void setRippleColor(Color color) {
circleRipple.setFill(color);
}
}
First you have to listen to parentPopupProperty changes of MenuItem. When you get the instance of parent popup than register listener for skinProperty of ContextMenu (parentPopup). When you get the skin then you can get MenuItemContainer which is Node equivalent of MenuItem and you can listen to widthProperty or heightProperty changes of MenuItemContainer.
Note: skinProperty change is fired just before ContextMenu is shown on the screen.
Custom class extending MenuItem class:
public class CstMenuItem extends MenuItem {
public CstMenuItem() {
// Create custom menu item listener.
new CstMenuItemListener(this);
}
/*
* Returns MenuItemContainer node associated with this menu item
* which can contain:
* 1. label node of type Label for displaying menu item text,
* 2. right node of type Label for displaying accelerator text,
* or an arrow if it's a Menu,
* 3. graphic node for displaying menu item icon, and
* 4. left node for displaying either radio button or check box.
*
* This is basically rewritten impl_styleableGetNode() which
* should not be used since it's marked as deprecated.
*/
public ContextMenuContent.MenuItemContainer getAssociatedNode() {
ContextMenu contextMenu = getParentPopup();
ContextMenuSkin skin = (ContextMenuSkin) contextMenu.getSkin();
ContextMenuContent content = (ContextMenuContent) skin.getNode();
// Items container contains MenuItemContainer nodes and Separator nodes.
Parent itemsContainer = content.getItemsContainer();
List<Node> children = itemsContainer.getChildrenUnmodifiable();
for (Node child : children) {
if (child instanceof ContextMenuContent.MenuItemContainer) {
ContextMenuContent.MenuItemContainer menuItemContainer =
(ContextMenuContent.MenuItemContainer) child;
if (menuItemContainer.getItem() == this) {
return menuItemContainer;
}
}
}
return null;
}
}
Custom MenuItem listener class:
public class CstMenuItemListener implements ChangeListener {
private CstMenuItem menuItem;
private ContextMenu parentPopup;
private Region menuItemContainer;
public CstMenuItemListener(CstMenuItem menuItem) {
this.menuItem = menuItem;
menuItem.parentPopupProperty().addListener(this);
}
#Override
public void changed(ObservableValue observable, Object oldValue, Object newValue) {
if (observable == menuItem.parentPopupProperty()) {
parentPopup = (ContextMenu) newValue;
parentPopup.skinProperty().addListener(this);
} else if (observable == parentPopup.skinProperty()) {
menuItemContainer = menuItem.getAssociatedNode();
menuItemContainer.widthProperty().addListener(this);
menuItemContainer.heightProperty().addListener(this);
} else if (observable == menuItemContainer.widthProperty()) {
System.out.println("width: " + (double) newValue);
} else if (observable == menuItemContainer.heightProperty()) {
System.out.println("height: " + (double) newValue);
}
}
}