如何在JavaFX桌面应用程序中使用Google Maps API?

时间:2016-06-05 23:54:48

标签: java google-maps google-maps-api-3 javafx

我想开始研究一个JavaFX桌面应用程序,该应用程序将使用Google Maps API进行大量工作(如果一切顺利)。我开始时遇到的困难比我想象的要困难,而且此时我只是想请某人提供一些代码示例,这些代码只需加载地图并在其上固定几个位置即可。任何帮助都会非常感激。到目前为止我能做的最好的事情就是在WebView中加载maps.google.com,这显然根本不涉及API,现在真的没有任何用处。

3 个答案:

答案 0 :(得分:6)

我只是想请某人提供一些代码示例,只需加载地图并在其上固定一些位置。

其中一种方法是使用HTML,Javascript和JavaFX的组合。在html和javascript中创建一个谷歌地图实例,然后使用WebEngine类调用JavaFX程序中的URL。 WebEngine加载网页,创建文档模型,根据需要应用样式,并在页面上运行JavaScript。之后,您将使用WebView显示谷歌地图内容,如:

final WebEngine webEngine = new WebEngine(getClass().getResource("googlemap.html").toString());
final WebView webView = new WebView(webEngine);

这是一个让你入门的link with sample codes

答案 1 :(得分:0)

您可以在没有网络视图的情况下使用某些API;只需使用Google客户端库...

添加以下依赖项:

enter image description here

例如,使用Places API:

您可以得到这个:

enter image description here

enter image description here

请确保您添加了Google API密钥并启用了结算功能...否则,一天最多只能进行1次轮询。

private static final String API_KEY = add API KEY HERE;

Main.java

import com.google.maps.model.AddressComponentType;
import com.google.maps.model.PlaceDetails;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import org.apache.commons.lang3.StringUtils;
import test.AutoCompleteAddressField.AddressPrediction;

public class Main extends Application
{

    @Override
    public void start(Stage primaryStage)
    {

        AutoCompleteAddressField text = new AutoCompleteAddressField();

        TextField streetField = new TextField();
        streetField.setPromptText("Street");

        TextField postalField = new TextField();
        postalField.setPromptText("PostalCode");

        TextField cityField = new TextField();
        cityField.setPromptText("City");

        TextField provinceField = new TextField();
        provinceField.setPromptText("Province");

        TextField countryField = new TextField();
        countryField.setPromptText("Country");

        Button clearButton = new Button("Clear");

        clearButton.setOnAction(e ->
        {
            text.clear();
            text.getEntries().clear();
            streetField.clear();
            postalField.clear();
            cityField.clear();
            provinceField.clear();
            countryField.clear();

        });

        text.getEntryMenu().setOnAction((ActionEvent e) ->
        {
            ((MenuItem) e.getTarget()).addEventHandler(Event.ANY, (Event event) ->
            {
                if (text.getLastSelectedObject() != null)
                {
                    text.setText(text.getLastSelectedObject().toString());
                    PlaceDetails place = AutoCompleteAddressField.getPlace((AddressPrediction) text.getLastSelectedObject());
                    if (place != null)
                    {
                        streetField.setText(
                                StringUtils.join(
                                        AutoCompleteAddressField.getComponentLongName(place.addressComponents, AddressComponentType.STREET_NUMBER),
                                        " ",
                                        AutoCompleteAddressField.getComponentLongName(place.addressComponents, AddressComponentType.ROUTE))
                        );

                        postalField.setText(AutoCompleteAddressField.getComponentLongName(place.addressComponents, AddressComponentType.POSTAL_CODE));
                        cityField.setText(AutoCompleteAddressField.getComponentLongName(place.addressComponents, AddressComponentType.LOCALITY));
                        provinceField.setText(AutoCompleteAddressField.getComponentLongName(place.addressComponents, AddressComponentType.ADMINISTRATIVE_AREA_LEVEL_1));
                        countryField.setText(AutoCompleteAddressField.getComponentLongName(place.addressComponents, AddressComponentType.COUNTRY));
                    } else
                    {
                        streetField.clear();
                        postalField.clear();
                        cityField.clear();
                        provinceField.clear();
                        countryField.clear();
                    }
                }
            });
        });

        VBox root = new VBox();
        root.getChildren().addAll(text, new Label(), streetField, postalField, provinceField, countryField, clearButton);

        Scene scene = new Scene(root, 300, 250);

        primaryStage.setTitle("Test Google Places API");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args)
    {
        launch(args);
    }

}

AutoCompleteAddressField.java

import com.google.maps.GeoApiContext;
import com.google.maps.PlaceDetailsRequest;
import com.google.maps.PlacesApi;
import com.google.maps.QueryAutocompleteRequest;
import com.google.maps.errors.ApiException;
import com.google.maps.model.AddressComponent;
import com.google.maps.model.AddressComponentType;
import com.google.maps.model.AutocompletePrediction;
import com.google.maps.model.PlaceDetails;
import java.io.IOException;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Platform;
import javafx.beans.value.ObservableValue;

public class AutoCompleteAddressField extends AutoCompleteTextField
{

    private static final String API_KEY = add API KEY HERE;

    public AutoCompleteAddressField()
    {
        super(new TreeSet<>((AddressPrediction o1, AddressPrediction o2) -> o1.toString().compareTo(o2.toString())));

        textProperty().addListener((ObservableValue<? extends String> o, String oldValue, String newValue) ->
        {
            if (newValue != null && !newValue.isEmpty())
            {
                new Thread(() ->
                {
                    AutocompletePrediction[] predictions = getPredictions(getText());

                    Platform.runLater(() ->
                    {
                        getEntries().clear();
                        for (AutocompletePrediction prediction : predictions)
                        {
                            getEntries().add(new AddressPrediction(prediction));
                        }
                    });
                }).start();

            }
        });
    }

    public class AddressPrediction
    {

        private final AutocompletePrediction prediction;

        public AddressPrediction(AutocompletePrediction prediction)
        {
            this.prediction = prediction;
        }

        @Override
        public String toString()
        {
            return prediction.description;
        }

        protected AutocompletePrediction getPrediction()
        {
            return this.prediction;
        }

    }

    public static PlaceDetails getPlace(AddressPrediction prediction)
    {
        if (prediction != null && prediction.getPrediction() != null && !prediction.getPrediction().placeId.isEmpty())
        {
            PlaceDetailsRequest query = PlacesApi.placeDetails(new GeoApiContext.Builder().apiKey(API_KEY).build(), prediction.getPrediction().placeId);
            return query.awaitIgnoreError();
        }
        return null;
    }

    public static AutocompletePrediction[] getPredictions(String userInput)
    {

        QueryAutocompleteRequest query = PlacesApi.queryAutocomplete(new GeoApiContext.Builder()
                .apiKey(API_KEY)
                .build(), userInput);
        try
        {
            return query.await();
        } catch (ApiException | InterruptedException | IOException ex)
        {
            Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
        }
        return new AutocompletePrediction[0];

    }

    public static String getComponentLongName(AddressComponent[] components, AddressComponentType type)
    {

        for (AddressComponent component : components)
        {
            for (AddressComponentType types : component.types)
            {
                if (types.equals(type))
                {
                    return component.longName;
                }
            }

        }
        return "";
    }
}

AutoCompleteTextField.java

import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.geometry.Side;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.CustomMenuItem;
import javafx.scene.control.TextField;

import java.util.LinkedList;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;

/**
 * This class is a TextField which implements an "autocomplete" functionality,
 * based on a supplied list of entries.<p>
 *
 * If the entered text matches a part of any of the supplied entries these are
 * going to be displayed in a popup. Further the matching part of the entry is
 * going to be displayed in a special style, defined by
 * {@link #textOccurenceStyle textOccurenceStyle}. The maximum number of
 * displayed entries in the popup is defined by
 * {@link #maxEntries maxEntries}.<br>
 * By default the pattern matching is not case-sensitive. This behaviour is
 * defined by the {@link #caseSensitive caseSensitive}
 * .<p>
 *
 * The AutoCompleteTextField also has a List of
 * {@link #filteredEntries filteredEntries} that is equal to the search results
 * if search results are not empty, or {@link #filteredEntries filteredEntries}
 * is equal to {@link #entries entries} otherwise. If
 * {@link #popupHidden popupHidden} is set to true no popup is going to be
 * shown. This list can be used to bind all entries to another node (a ListView
 * for example) in the following way:
 * <pre>
 * <code>
 * AutoCompleteTextField auto = new AutoCompleteTextField(entries);
 * auto.setPopupHidden(true);
 * SimpleListProperty filteredEntries = new SimpleListProperty(auto.getFilteredEntries());
 * listView.itemsProperty().bind(filteredEntries);
 * </code>
 * </pre>
 *
 * @author Caleb Brinkman
 * @author Fabian Ochmann
 * @param <S>
 */
public class AutoCompleteTextField<S> extends TextField
{

    private final ObjectProperty<S> lastSelectedItem = new SimpleObjectProperty<>();

    /**
     * The existing autocomplete entries.
     */
    private final SortedSet<S> entries;

    /**
     * The set of filtered entries:<br>
     * Equal to the search results if search results are not empty, equal to
     * {@link #entries entries} otherwise.
     */
    private ObservableList<S> filteredEntries
            = FXCollections.observableArrayList();

    /**
     * The popup used to select an entry.
     */
    private ContextMenu entriesPopup;

    /**
     * Indicates whether the search is case sensitive or not. <br>
     * Default: false
     */
    private boolean caseSensitive = false;

    /**
     * Indicates whether the Popup should be hidden or displayed. Use this if
     * you want to filter an existing list/set (for example values of a
     * {@link javafx.scene.control.ListView ListView}). Do this by binding
     * {@link #getFilteredEntries() getFilteredEntries()} to the list/set.
     */
    private boolean popupHidden = false;

    /**
     * The CSS style that should be applied on the parts in the popup that match
     * the entered text. <br>
     * Default: "-fx-font-weight: bold; -fx-fill: red;"
     * <p>
     * Note: This style is going to be applied on an
     * {@link javafx.scene.text.Text Text} instance. See the <i>JavaFX CSS
     * Reference Guide</i> for available CSS Propeties.
     */
    private String textOccurenceStyle = "-fx-font-weight: bold; "
          + "-fx-fill: rgb(66,139,202);";

    /**
     * The maximum Number of entries displayed in the popup.<br>
     * Default: 10
     */
    private int maxEntries = 10;

    /**
     * Construct a new AutoCompleteTextField.
     *
     * @param entrySet
     */
    public AutoCompleteTextField(SortedSet<S> entrySet)
    {
        super();
        this.entries = (entrySet == null ? new TreeSet<>() : entrySet);
        this.filteredEntries.addAll(entries);

        entriesPopup = new ContextMenu();

        textProperty().addListener((ObservableValue<? extends String> observableValue, String s, String s2) ->
        {

            if (getText() == null || getText().length() == 0)
            {
                filteredEntries.clear();
                filteredEntries.addAll(entries);
                entriesPopup.hide();
            } else
            {
                LinkedList<S> searchResult = new LinkedList<>();
                //Check if the entered Text is part of some entry
                String text1 = getText();
                Pattern pattern;
                if (isCaseSensitive())
                {
                    pattern = Pattern.compile(".*" + text1 + ".*");
                } else
                {
                    pattern = Pattern.compile(".*" + text1 + ".*", Pattern.CASE_INSENSITIVE);
                }
                for (S entry : entries)
                {
                    Matcher matcher = pattern.matcher(entry.toString());
                    if (matcher.matches())
                    {
                        searchResult.add(entry);
                    }
                }
                if (!entries.isEmpty())
                {
                    filteredEntries.clear();
                    filteredEntries.addAll(searchResult);
                    //Only show popup if not in filter mode
                    if (!isPopupHidden())
                    {
                        populatePopup(searchResult, text1);
                        if (!entriesPopup.isShowing())
                        {
                            entriesPopup.show(AutoCompleteTextField.this, Side.BOTTOM, 0, 0);
                        }
                    }
                } else
                {
                    entriesPopup.hide();
                }
            }
        });

        focusedProperty().addListener((ObservableValue<? extends Boolean> observableValue, Boolean aBoolean, Boolean aBoolean2) ->
        {
            entriesPopup.hide();
        });

    }

    /**
     * Get the existing set of autocomplete entries.
     *
     * @return The existing autocomplete entries.
     */
    public SortedSet<S> getEntries()
    {
        return entries;
    }

    /**
     * Populate the entry set with the given search results. Display is limited
     * to 10 entries, for performance.
     *
     * @param searchResult The set of matching strings.
     */
    private void populatePopup(List<S> searchResult, String text)
    {
        List<CustomMenuItem> menuItems = new LinkedList<>();
        int count = Math.min(searchResult.size(), getMaxEntries());
        for (int i = 0; i < count; i++)
        {
            final String result = searchResult.get(i).toString();
            final S itemObject = searchResult.get(i);
            int occurence;

            if (isCaseSensitive())
            {
                occurence = result.indexOf(text);
            } else
            {
                occurence = result.toLowerCase().indexOf(text.toLowerCase());
            }
            if (occurence < 0)
            {
                continue;
            }
            //Part before occurence (might be empty)
            Text pre = new Text(result.substring(0, occurence));
            //Part of (first) occurence
            Text in = new Text(result.substring(occurence, occurence + text.length()));
            in.setStyle(getTextOccurenceStyle());
            //Part after occurence
            Text post = new Text(result.substring(occurence + text.length(), result.length()));

            TextFlow entryFlow = new TextFlow(pre, in, post);

            CustomMenuItem item = new CustomMenuItem(entryFlow, true);
            item.setOnAction((ActionEvent actionEvent) ->
            {
                lastSelectedItem.set(itemObject);
                entriesPopup.hide();
            });
            menuItems.add(item);
        }
        entriesPopup.getItems().clear();
        entriesPopup.getItems().addAll(menuItems);

    }

    public S getLastSelectedObject()
    {
        return lastSelectedItem.get();
    }

    public ContextMenu getEntryMenu()
    {
        return entriesPopup;
    }

    public boolean isCaseSensitive()
    {
        return caseSensitive;
    }

    public String getTextOccurenceStyle()
    {
        return textOccurenceStyle;
    }

    public void setCaseSensitive(boolean caseSensitive)
    {
        this.caseSensitive = caseSensitive;
    }

    public void setTextOccurenceStyle(String textOccurenceStyle)
    {
        this.textOccurenceStyle = textOccurenceStyle;
    }

    public boolean isPopupHidden()
    {
        return popupHidden;
    }

    public void setPopupHidden(boolean popupHidden)
    {
        this.popupHidden = popupHidden;
    }

    public ObservableList<S> getFilteredEntries()
    {
        return filteredEntries;
    }

    public int getMaxEntries()
    {
        return maxEntries;
    }

    public void setMaxEntries(int maxEntries)
    {
        this.maxEntries = maxEntries;
    }

}

答案 2 :(得分:-1)

您可以在没有网络视图的情况下使用某些API;只需使用Google客户端库...

添加以下依赖项:

enter image description here

例如,使用Places API:

您可以得到这个:

enter image description here

enter image description here

请确保您添加了Google API密钥并启用了结算功能...否则,一天最多只能进行1次轮询。

private static final String API_KEY = add API KEY HERE;

Main.java

import com.google.maps.model.AddressComponentType;
import com.google.maps.model.PlaceDetails;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import org.apache.commons.lang3.StringUtils;
import test.AutoCompleteAddressField.AddressPrediction;

public class Main extends Application
{

    @Override
    public void start(Stage primaryStage)
    {

        AutoCompleteAddressField text = new AutoCompleteAddressField();

        TextField streetField = new TextField();
        streetField.setPromptText("Street");

        TextField postalField = new TextField();
        postalField.setPromptText("PostalCode");

        TextField cityField = new TextField();
        cityField.setPromptText("City");

        TextField provinceField = new TextField();
        provinceField.setPromptText("Province");

        TextField countryField = new TextField();
        countryField.setPromptText("Country");

        Button clearButton = new Button("Clear");

        clearButton.setOnAction(e ->
        {
            text.clear();
            text.getEntries().clear();
            streetField.clear();
            postalField.clear();
            cityField.clear();
            provinceField.clear();
            countryField.clear();

        });

        text.getEntryMenu().setOnAction((ActionEvent e) ->
        {
            ((MenuItem) e.getTarget()).addEventHandler(Event.ANY, (Event event) ->
            {
                if (text.getLastSelectedObject() != null)
                {
                    text.setText(text.getLastSelectedObject().toString());
                    PlaceDetails place = AutoCompleteAddressField.getPlace((AddressPrediction) text.getLastSelectedObject());
                    if (place != null)
                    {
                        streetField.setText(
                                StringUtils.join(
                                        AutoCompleteAddressField.getComponentLongName(place.addressComponents, AddressComponentType.STREET_NUMBER),
                                        " ",
                                        AutoCompleteAddressField.getComponentLongName(place.addressComponents, AddressComponentType.ROUTE))
                        );

                        postalField.setText(AutoCompleteAddressField.getComponentLongName(place.addressComponents, AddressComponentType.POSTAL_CODE));
                        cityField.setText(AutoCompleteAddressField.getComponentLongName(place.addressComponents, AddressComponentType.LOCALITY));
                        provinceField.setText(AutoCompleteAddressField.getComponentLongName(place.addressComponents, AddressComponentType.ADMINISTRATIVE_AREA_LEVEL_1));
                        countryField.setText(AutoCompleteAddressField.getComponentLongName(place.addressComponents, AddressComponentType.COUNTRY));
                    } else
                    {
                        streetField.clear();
                        postalField.clear();
                        cityField.clear();
                        provinceField.clear();
                        countryField.clear();
                    }
                }
            });
        });

        VBox root = new VBox();
        root.getChildren().addAll(text, new Label(), streetField, postalField, provinceField, countryField, clearButton);

        Scene scene = new Scene(root, 300, 250);

        primaryStage.setTitle("Test Google Places API");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args)
    {
        launch(args);
    }

}

AutoCompleteAddressField.java

import com.google.maps.GeoApiContext;
import com.google.maps.PlaceDetailsRequest;
import com.google.maps.PlacesApi;
import com.google.maps.QueryAutocompleteRequest;
import com.google.maps.errors.ApiException;
import com.google.maps.model.AddressComponent;
import com.google.maps.model.AddressComponentType;
import com.google.maps.model.AutocompletePrediction;
import com.google.maps.model.PlaceDetails;
import java.io.IOException;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Platform;
import javafx.beans.value.ObservableValue;

public class AutoCompleteAddressField extends AutoCompleteTextField
{

    private static final String API_KEY = add API KEY HERE;

    public AutoCompleteAddressField()
    {
        super(new TreeSet<>((AddressPrediction o1, AddressPrediction o2) -> o1.toString().compareTo(o2.toString())));

        textProperty().addListener((ObservableValue<? extends String> o, String oldValue, String newValue) ->
        {
            if (newValue != null && !newValue.isEmpty())
            {
                new Thread(() ->
                {
                    AutocompletePrediction[] predictions = getPredictions(getText());

                    Platform.runLater(() ->
                    {
                        getEntries().clear();
                        for (AutocompletePrediction prediction : predictions)
                        {
                            getEntries().add(new AddressPrediction(prediction));
                        }
                    });
                }).start();

            }
        });
    }

    public class AddressPrediction
    {

        private final AutocompletePrediction prediction;

        public AddressPrediction(AutocompletePrediction prediction)
        {
            this.prediction = prediction;
        }

        @Override
        public String toString()
        {
            return prediction.description;
        }

        protected AutocompletePrediction getPrediction()
        {
            return this.prediction;
        }

    }

    public static PlaceDetails getPlace(AddressPrediction prediction)
    {
        if (prediction != null && prediction.getPrediction() != null && !prediction.getPrediction().placeId.isEmpty())
        {
            PlaceDetailsRequest query = PlacesApi.placeDetails(new GeoApiContext.Builder().apiKey(API_KEY).build(), prediction.getPrediction().placeId);
            return query.awaitIgnoreError();
        }
        return null;
    }

    public static AutocompletePrediction[] getPredictions(String userInput)
    {

        QueryAutocompleteRequest query = PlacesApi.queryAutocomplete(new GeoApiContext.Builder()
                .apiKey(API_KEY)
                .build(), userInput);
        try
        {
            return query.await();
        } catch (ApiException | InterruptedException | IOException ex)
        {
            Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
        }
        return new AutocompletePrediction[0];

    }

    public static String getComponentLongName(AddressComponent[] components, AddressComponentType type)
    {

        for (AddressComponent component : components)
        {
            for (AddressComponentType types : component.types)
            {
                if (types.equals(type))
                {
                    return component.longName;
                }
            }

        }
        return "";
    }
}

AutoCompleteTextField.java

import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.geometry.Side;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.CustomMenuItem;
import javafx.scene.control.TextField;

import java.util.LinkedList;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;

/**
 * This class is a TextField which implements an "autocomplete" functionality,
 * based on a supplied list of entries.<p>
 *
 * If the entered text matches a part of any of the supplied entries these are
 * going to be displayed in a popup. Further the matching part of the entry is
 * going to be displayed in a special style, defined by
 * {@link #textOccurenceStyle textOccurenceStyle}. The maximum number of
 * displayed entries in the popup is defined by
 * {@link #maxEntries maxEntries}.<br>
 * By default the pattern matching is not case-sensitive. This behaviour is
 * defined by the {@link #caseSensitive caseSensitive}
 * .<p>
 *
 * The AutoCompleteTextField also has a List of
 * {@link #filteredEntries filteredEntries} that is equal to the search results
 * if search results are not empty, or {@link #filteredEntries filteredEntries}
 * is equal to {@link #entries entries} otherwise. If
 * {@link #popupHidden popupHidden} is set to true no popup is going to be
 * shown. This list can be used to bind all entries to another node (a ListView
 * for example) in the following way:
 * <pre>
 * <code>
 * AutoCompleteTextField auto = new AutoCompleteTextField(entries);
 * auto.setPopupHidden(true);
 * SimpleListProperty filteredEntries = new SimpleListProperty(auto.getFilteredEntries());
 * listView.itemsProperty().bind(filteredEntries);
 * </code>
 * </pre>
 *
 * @author Caleb Brinkman
 * @author Fabian Ochmann
 * @param <S>
 */
public class AutoCompleteTextField<S> extends TextField
{

    private final ObjectProperty<S> lastSelectedItem = new SimpleObjectProperty<>();

    /**
     * The existing autocomplete entries.
     */
    private final SortedSet<S> entries;

    /**
     * The set of filtered entries:<br>
     * Equal to the search results if search results are not empty, equal to
     * {@link #entries entries} otherwise.
     */
    private ObservableList<S> filteredEntries
            = FXCollections.observableArrayList();

    /**
     * The popup used to select an entry.
     */
    private ContextMenu entriesPopup;

    /**
     * Indicates whether the search is case sensitive or not. <br>
     * Default: false
     */
    private boolean caseSensitive = false;

    /**
     * Indicates whether the Popup should be hidden or displayed. Use this if
     * you want to filter an existing list/set (for example values of a
     * {@link javafx.scene.control.ListView ListView}). Do this by binding
     * {@link #getFilteredEntries() getFilteredEntries()} to the list/set.
     */
    private boolean popupHidden = false;

    /**
     * The CSS style that should be applied on the parts in the popup that match
     * the entered text. <br>
     * Default: "-fx-font-weight: bold; -fx-fill: red;"
     * <p>
     * Note: This style is going to be applied on an
     * {@link javafx.scene.text.Text Text} instance. See the <i>JavaFX CSS
     * Reference Guide</i> for available CSS Propeties.
     */
    private String textOccurenceStyle = "-fx-font-weight: bold; "
          + "-fx-fill: rgb(66,139,202);";

    /**
     * The maximum Number of entries displayed in the popup.<br>
     * Default: 10
     */
    private int maxEntries = 10;

    /**
     * Construct a new AutoCompleteTextField.
     *
     * @param entrySet
     */
    public AutoCompleteTextField(SortedSet<S> entrySet)
    {
        super();
        this.entries = (entrySet == null ? new TreeSet<>() : entrySet);
        this.filteredEntries.addAll(entries);

        entriesPopup = new ContextMenu();

        textProperty().addListener((ObservableValue<? extends String> observableValue, String s, String s2) ->
        {

            if (getText() == null || getText().length() == 0)
            {
                filteredEntries.clear();
                filteredEntries.addAll(entries);
                entriesPopup.hide();
            } else
            {
                LinkedList<S> searchResult = new LinkedList<>();
                //Check if the entered Text is part of some entry
                String text1 = getText();
                Pattern pattern;
                if (isCaseSensitive())
                {
                    pattern = Pattern.compile(".*" + text1 + ".*");
                } else
                {
                    pattern = Pattern.compile(".*" + text1 + ".*", Pattern.CASE_INSENSITIVE);
                }
                for (S entry : entries)
                {
                    Matcher matcher = pattern.matcher(entry.toString());
                    if (matcher.matches())
                    {
                        searchResult.add(entry);
                    }
                }
                if (!entries.isEmpty())
                {
                    filteredEntries.clear();
                    filteredEntries.addAll(searchResult);
                    //Only show popup if not in filter mode
                    if (!isPopupHidden())
                    {
                        populatePopup(searchResult, text1);
                        if (!entriesPopup.isShowing())
                        {
                            entriesPopup.show(AutoCompleteTextField.this, Side.BOTTOM, 0, 0);
                        }
                    }
                } else
                {
                    entriesPopup.hide();
                }
            }
        });

        focusedProperty().addListener((ObservableValue<? extends Boolean> observableValue, Boolean aBoolean, Boolean aBoolean2) ->
        {
            entriesPopup.hide();
        });

    }

    /**
     * Get the existing set of autocomplete entries.
     *
     * @return The existing autocomplete entries.
     */
    public SortedSet<S> getEntries()
    {
        return entries;
    }

    /**
     * Populate the entry set with the given search results. Display is limited
     * to 10 entries, for performance.
     *
     * @param searchResult The set of matching strings.
     */
    private void populatePopup(List<S> searchResult, String text)
    {
        List<CustomMenuItem> menuItems = new LinkedList<>();
        int count = Math.min(searchResult.size(), getMaxEntries());
        for (int i = 0; i < count; i++)
        {
            final String result = searchResult.get(i).toString();
            final S itemObject = searchResult.get(i);
            int occurence;

            if (isCaseSensitive())
            {
                occurence = result.indexOf(text);
            } else
            {
                occurence = result.toLowerCase().indexOf(text.toLowerCase());
            }
            if (occurence < 0)
            {
                continue;
            }
            //Part before occurence (might be empty)
            Text pre = new Text(result.substring(0, occurence));
            //Part of (first) occurence
            Text in = new Text(result.substring(occurence, occurence + text.length()));
            in.setStyle(getTextOccurenceStyle());
            //Part after occurence
            Text post = new Text(result.substring(occurence + text.length(), result.length()));

            TextFlow entryFlow = new TextFlow(pre, in, post);

            CustomMenuItem item = new CustomMenuItem(entryFlow, true);
            item.setOnAction((ActionEvent actionEvent) ->
            {
                lastSelectedItem.set(itemObject);
                entriesPopup.hide();
            });
            menuItems.add(item);
        }
        entriesPopup.getItems().clear();
        entriesPopup.getItems().addAll(menuItems);

    }

    public S getLastSelectedObject()
    {
        return lastSelectedItem.get();
    }

    public ContextMenu getEntryMenu()
    {
        return entriesPopup;
    }

    public boolean isCaseSensitive()
    {
        return caseSensitive;
    }

    public String getTextOccurenceStyle()
    {
        return textOccurenceStyle;
    }

    public void setCaseSensitive(boolean caseSensitive)
    {
        this.caseSensitive = caseSensitive;
    }

    public void setTextOccurenceStyle(String textOccurenceStyle)
    {
        this.textOccurenceStyle = textOccurenceStyle;
    }

    public boolean isPopupHidden()
    {
        return popupHidden;
    }

    public void setPopupHidden(boolean popupHidden)
    {
        this.popupHidden = popupHidden;
    }

    public ObservableList<S> getFilteredEntries()
    {
        return filteredEntries;
    }

    public int getMaxEntries()
    {
        return maxEntries;
    }

    public void setMaxEntries(int maxEntries)
    {
        this.maxEntries = maxEntries;
    }

}