Displaying a large list in a Pane in JavaFX - arrays

In my programme I've a large array of strings(say 1600) which I want to show as a CheckBox list. The array is actually the location of all the songs in one's PC, and thus can gradually be bigger. I don't wanna use ListView<String> as the CheckBox list is more efficient and above all visually better for my purpose. I'm currently doing the below :
private void listAll() {
songs = MediaManager.getAllSongs();
VBox vb = new VBox();
vb.setSpacing(5);
vb.getStyleClass().add("background");
if (songs != null) {
Service s = new Service() {
#Override
protected Task createTask() {
Task t = new Task() {
#Override
protected Object call() throws Exception {
for (String song : songs) {
addSong(song, vb);
c++;
updateMessage(c+" songs");
}
return null;
}
};
t.messageProperty().addListener((obs,o,n)->{
count.setText(n);
});
return t;
}
};
s.start();
ScrollPane sp = new ScrollPane(vb);
getChildren().add(sp);
}
}
private void addSong(String n, Pane p) {
String toAdd = "";
Media m = new Media(Paths.get(n).toUri().toString());
if (m.getMetadata().get("title") == null || !(m.getMetadata().get("title").equals(""))) {
toAdd = m.getSource().split("/")[m.getSource().split("/").length - 1].replace("%20", " ").replace(".mp3", "");
} else {
toAdd = ((String) m.getMetadata().get("title"));
}
SongBox s = new SongBox(toAdd);
s.setUserData(n);
p.getChildren().add(s);
}
class SongBox extends CheckBox {
public SongBox(String t) {
this();
setText(t);
}
public SongBox() {
super();
setOnAction((ActionEvent evt) -> {
if (isSelected()) {
if (!playNow.isVisible()) {
playNow.setVisible(true);
}
path = (String) getUserData();
selected.add((String) getUserData());
} else {
selected.remove((String) getUserData());
if (selected.size() == 0) {
playNow.setVisible(false);
}
}
});
}
}
First of all, that is not showing the complete array. Whenever I'm going back and returning to it, the number of songs get changed. Secondly, the whole UI is getting sluggish(sometimes also hanging my PC). Moreover, I can't cancel the Service when I've gone to the previous window, as it's always returning false. Anyone have a better approach?

Related

JavaFX : Get the selected value when CheckBoxTreeItem clicked

I want to get all the selected values in TreeView containing CheckBoxTreeItems.
How to accomplish this?
You can listen to the events on the root to keep a Set<TreeItem> up to date or you can do a DFS for selected items starting at the root.
Note that the DFS approach certainly requires the smaller amount of code, but the other approach allows you to keep the set up to date during modifications...
#Override
public void start(Stage primaryStage) {
CheckBoxTreeItem<String> root = new CheckBoxTreeItem<>("root");
CheckBoxTreeItem<String> c1 = new CheckBoxTreeItem<>("c1");
c1.getChildren().addAll(
new CheckBoxTreeItem<>("c1.1"),
new CheckBoxTreeItem<>("c1.2"),
new CheckBoxTreeItem<>("c1.3")
);
CheckBoxTreeItem<String> c2 = new CheckBoxTreeItem<>("c2");
c2.getChildren().addAll(
new CheckBoxTreeItem<>("c2.1"),
new CheckBoxTreeItem<>("c2.2"),
new CheckBoxTreeItem<>("c2.3")
);
CheckBoxTreeItem<String> c3 = new CheckBoxTreeItem<>("c3");
c3.getChildren().addAll(
new CheckBoxTreeItem<>("c3.1"),
new CheckBoxTreeItem<>("c3.2"),
new CheckBoxTreeItem<>("c3.3")
);
root.getChildren().addAll(c1, c2, c3);
TreeView<String> treeView = new TreeView<>(root);
treeView.setCellFactory(CheckBoxTreeCell.forTreeView());
Set<TreeItem<String>> selected = new HashSet<>();
// listen for selection change
root.addEventHandler(CheckBoxTreeItem.checkBoxSelectionChangedEvent(), (CheckBoxTreeItem.TreeModificationEvent<String> evt) -> {
CheckBoxTreeItem<String> item = evt.getTreeItem();
if (evt.wasIndeterminateChanged()) {
if (item.isIndeterminate()) {
selected.remove(item);
} else if (item.isSelected()) {
selected.add(item);
}
} else if (evt.wasSelectionChanged()) {
if (item.isSelected()) {
selected.add(item);
} else {
selected.remove(item);
}
}
});
// listen for subtree add/remove
root.addEventHandler(TreeItem.childrenModificationEvent(), (TreeItem.TreeModificationEvent<String> evt) -> {
if (evt.wasAdded()) {
for (TreeItem<String> added : evt.getAddedChildren()) {
addSubtree(selected, (CheckBoxTreeItem<String>) added);
}
}
if (evt.wasRemoved()) {
for (TreeItem<String> removed : evt.getRemovedChildren()) {
removeSubtree(selected, (CheckBoxTreeItem<String>) removed);
}
}
});
Button button = new Button("print selected");
button.setOnAction(evt -> {
System.out.println("----------------");
selected.stream().map(TreeItem::getValue).forEach(System.out::println);
});
Button button2 = new Button("print dfs");
button2.setOnAction(evt -> {
System.out.println("----------------");
print(root);
});
Button remove = new Button("remove");
remove.setOnAction(evt -> {
root.getChildren().remove(c3);
});
Scene scene = new Scene(new VBox(treeView, button, button2, remove));
primaryStage.setScene(scene);
primaryStage.show();
}
private static <T> void removeSubtree(Collection<TreeItem<T>> collection, CheckBoxTreeItem<T> item) {
if (item.isSelected()) {
collection.remove(item);
} else if (!item.isIndeterminate() && !item.isIndependent()) {
return;
}
for (TreeItem<T> child : item.getChildren()) {
removeSubtree(collection, (CheckBoxTreeItem<T>) child);
}
}
private static <T> void addSubtree(Collection<TreeItem<T>> collection, CheckBoxTreeItem<T> item) {
if (item.isSelected()) {
collection.add(item);
} else if (!item.isIndeterminate() && !item.isIndependent()) {
return;
}
for (TreeItem<T> child : item.getChildren()) {
addSubtree(collection, (CheckBoxTreeItem<T>) child);
}
}
private static <T> void print(CheckBoxTreeItem<T> item) {
if (item.isSelected()) {
System.out.println(item.getValue());
} else if (!item.isIndeterminate() && !item.isIndependent()) {
return;
}
for (TreeItem<T> child : item.getChildren()) {
print((CheckBoxTreeItem<T>) child);
}
}
If you're using a TreeView, you can get the item selected like this:
treeView.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<TreeItem<String>>() {
#Override
public void changed(ObservableValue<? extends TreeItem<String>> observable, TreeItem<String> oldValue,
TreeItem<String> newValue) {
System.out.println("The selected item is : "newValue.getValue());
}
});

How to design multipane with 3 fragment using Android-PanesLibrary?

Good Day Developers,
I already implement this fantastic library called "Android-PanesLibrary" by Kenrick Rilee. and what i want to achive is something like this.
But i end up doing like this :
my first problem if in showDetails method i delete the comment symbol, it will showing up an error. but if i make the method empty, it will run just like the second image.
my objective is how can this be done just using string array data?
Any ideas or help would be greatly appreciated.
Environment : Windows 7, Android Studio, Genymotion.
This is MainMenuFragment.java :
public class MainMenuFragment extends android.app.ListFragment {
private static int sExampleNum = 0;
protected final String TAG = "mainmenuFragment" ;
#ViewById(R.id.menu_listview)
protected ListView menuListView ;
private View parentView;
int mCurCheckPosition = 0;
public MainMenuFragment() {
super();
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Resources res = getResources();
String [] mainmenulistview = res.getStringArray(R.array.listview_main_menu);
ArrayAdapter<String> connectArrayToListView = new ArrayAdapter<String>(getActivity(),android.R.layout.simple_list_item_activated_1,mainmenulistview);
setListAdapter(connectArrayToListView);
if (savedInstanceState != null) {
mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
}
getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
showDetails(mCurCheckPosition);
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("curChoice", mCurCheckPosition);
}
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
showDetails(position);
}
// if I un-comment on method bellow, it will result an error.
void showDetails(int index) {
//mCurCheckPosition = index;
//getListView().setItemChecked(index, true);
//PCDesktopFragment_ pcDesktop = (PCDesktopFragment_) getFragmentManager().findFragmentById(R.id.sub_one_fragment);
//if (pcDesktop == null || pcDesktop.getShownIndex() != index) {
// welder_pipe_reg = PCDesktopFragment_.newInstance(index);
// android.app.FragmentTransaction ft = getFragmentManager().beginTransaction();
// ft.replace(R.id.sub_one_fragment, pcDesktop);
// ft.commit();
//}
}
}
and then i already create a class called PCDesktopFragment.java that extends ListFragment (this should be showing up on second fragment using listfragment)
#EFragment(R.layout.sub_one_menu)
public class PCDesktopFragment_ extends ListFragment {
View v;
public static int i;
public static PCDesktopFragment_ newInstance(int index){
PCDesktopFragment_ f = new PCDesktopFragment_();
Bundle args = new Bundle();
args.putInt("index", index);
index = i;
f.setArguments(args);
return f;
}
public int getShownIndex() {
return getArguments().getInt("index", 0);
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
inflater.inflate(R.layout.sub_one_menu, container, false);
return super.onCreateView(inflater, container, savedInstanceState);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (i == 0) {
String [] sub_a = {"Test1","Test2"};
setListAdapter(new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, sub_a));
}
}
//#ItemClick(R.id.sub_one_listview)
//protected void handleDomainClick(int position) {
// Fragment f = null ;
// if (position == 0) {
// f = new PCDesktopFragment_();
// }
// Activity a = getActivity();
// if (f != null && a != null && a instanceof FragmentLauncher)
// ((FragmentLauncher) a).addFragment(this, f);
//}
}

combobox jump to typed char

I stumbled on (in my eyes) a silly problem. However I don't find a solution for this (maybe because of not using the right search keywords, or by making it too difficult when it can be easy..)
Scenario:
I have a combobox with 500 customers. I have to select a single costumer.
In Swing, when the list was down and you started typing, it automatically jumps to the typed letter. E.g.:
Items:
Adam
Dirk
Freddy
...
Roger
Steven
Z person
When the combobox list is open, I just type 'R' and, in swing, it jumped to the first customer starting with 'R'. In javafx 2 it seems it does not have that behaviour... Is there some option that I have to enable or should I do something like using an editable combobox instead and make a filter() method that is fired on every keypress?
Edit: sollution based on Bhupendra's answer:
public class FilterComboBox<T> extends ComboBox<T> {
private final FilterComboBox<T> fcbo = this;
//private FilterComboBox fcbo = this;
private ObservableList<T> items;
private ObservableList<T> filter;
private String s;
private Object selection;
private class KeyHandler implements EventHandler< KeyEvent> {
private SingleSelectionModel<T> sm;
public KeyHandler() {
sm = getSelectionModel();
s = "";
}
#Override
public void handle(KeyEvent event) {
filter.clear();
// handle non alphanumeric keys like backspace, delete etc
if (event.getCode() == KeyCode.BACK_SPACE && s.length() > 0) {
s = s.substring(0, s.length() - 1);
} else {
s += event.getText();
}
if (s.length() == 0) {
fcbo.setItems(items);
sm.selectFirst();
return;
}
//System.out.println(s);
if (event.getCode().isLetterKey()) {
for (T item : items) {
if (item.toString().toUpperCase().startsWith(s.toUpperCase())) {
filter.add(item);
//System.out.println(item);
fcbo.setItems(filter);
//sm.clearSelection();
//sm.select(item);
}
}
sm.select(0);
}
}
}
public FilterComboBox(final ObservableList<T> items) {
super(items);
this.items = items;
this.filter = FXCollections.observableArrayList();
setOnKeyReleased(new KeyHandler());
this.focusedProperty().addListener(new ChangeListener() {
#Override
public void changed(ObservableValue observable, Object oldValue, Object newValue) {
if (newValue == false) {
s = "";
fcbo.setItems(items);
fcbo.getSelectionModel().select((T)selection);
}
}
});
this.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() {
#Override
public void changed(ObservableValue observable, Object oldValue, Object newValue) {
if (newValue != null) {
selection = (Object) newValue;
}
}
});
}
}
The simplest form of a filter combo box would be as the code below. But it would need more work to refine it.
Also, if the list is huge, as in your case, there might be a performance issues as we are looping thru' the entire collection on each key press.
public class FilterComboBox extends ComboBox< String > {
private ObservableList< String > items;
private class KeyHandler implements EventHandler< KeyEvent > {
private SingleSelectionModel< String > sm;
private String s;
public KeyHandler() {
sm = getSelectionModel();
s = "";
}
#Override
public void handle( KeyEvent event ) {
// handle non alphanumeric keys like backspace, delete etc
if( event.getCode() == KeyCode.BACK_SPACE && s.length()>0)
s = s.substring( 0, s.length() - 1 );
else s += event.getText();
if( s.length() == 0 ) {
sm.selectFirst();
return;
}
System.out.println( s );
for( String item: items ) {
if( item.startsWith( s ) ) sm.select( item );
}
}
}
public FilterComboBox( ObservableList< String > items ) {
super( items );
this.items = items;
setOnKeyReleased( new KeyHandler() );
}
}
Wouldn't code like this be sufficient?
comboBox.setOnKeyReleased(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
String s = jumpTo(event.getText(), comboBox.getValue(), comboBox.getItems());
if (s != null) {
comboBox.setValue(s);
}
}
});
...
static String jumpTo(String keyPressed, String currentlySelected, List<String> items) {
String key = keyPressed.toUpperCase();
if (key.matches("^[A-Z]$")) {
// Only act on letters so that navigating with cursor keys does not
// try to jump somewhere.
boolean letterFound = false;
boolean foundCurrent = currentlySelected == null;
for (String s : items) {
if (s.toUpperCase().startsWith(key)) {
letterFound = true;
if (foundCurrent) {
return s;
}
foundCurrent = s.equals(currentlySelected);
}
}
if (letterFound) {
return jumpTo(keyPressed, null, items);
}
}
return null;
}
This will jump to the first item when you press a letter. If you press that letter again, it jumps to the next item starting with that letter, wrapping back to the first if there are no more items starting with that letter.
I could not really get Perneel's solution to suit my needs. Bhupendra's was nice but there was one detail : it selects the last matching item. If you have numbers from 0 to 20 (as String), it would return 19 instead of 1 if "1" is typed...
The code below adds the line requiered to solve this.
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.control.ComboBox;
import javafx.scene.control.SingleSelectionModel;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
// TODO: Auto-generated Javadoc
/**
* The Class FilterComboBox.
*/
public class FilterComboBox extends ComboBox< String >
{
/** The items. */
private ObservableList< String > items;
/**
* The Class KeyHandler.
*/
private class KeyHandler implements EventHandler< KeyEvent >
{
/** The sm. */
private SingleSelectionModel< String > sm;
/** The s. */
private String s;
/**
* Instantiates a new key handler.
*/
public KeyHandler()
{
sm = getSelectionModel();
s = "";
}
/* (non-Javadoc)
* #see javafx.event.EventHandler#handle(javafx.event.Event)
*/
#Override
public void handle( KeyEvent event )
{
// handle non alphanumeric keys like backspace, delete etc
if( event.getCode() == KeyCode.BACK_SPACE && s.length()>0)
{
s = s.substring( 0, s.length() - 1 );
}
else if(event.getCode() != KeyCode.TAB )
{
s += event.getText();
}
if( s.length() == 0 )
{
sm.selectFirst();
return;
}
System.out.println( s );
for( String item: items )
{
if( item.startsWith( s ) )
{
sm.select( item );
return;
}
}
}
}
/**
* Instantiates a new filter combo box.
*
* #param items the items
*/
public FilterComboBox( ObservableList< String > items )
{
super( items );
this.items = items;
setOnKeyReleased( new KeyHandler() );
}
}
This component is a ComboBox that only takes String as an input and which can be filtered by typing some character. All credit goes to Bhupendra, I only posted this code so as to prevent other people from having to think too much about this common problem.
Last edit : added a test to prevent TAB from being considered as a character (allow to navigate in a form without breaking the component)
Here is another option how to do it.
An example from the site https://tech.chitgoks.com was taken as the basis.
It features an elegant solution, which, if desired, can be used in the previous examples too.
When typing, the list automatically scrolls, it is very convenient.
import com.sun.javafx.scene.control.skin.ComboBoxListViewSkin;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import java.time.Duration;
import java.time.Instant;
import java.util.Collection;
class SearchComboBox<T> extends ComboBox<T> {
private static final int IDLE_INTERVAL_MILLIS = 1000;
private Instant instant = Instant.now();
private StringBuilder sb = new StringBuilder();
public SearchComboBox(Collection<T> choices) {
this(FXCollections.observableArrayList(choices));
}
public SearchComboBox(final ObservableList<T> items) {
this();
setItems(items);
getSelectionModel().selectFirst();
}
public SearchComboBox() {
super();
this.addEventFilter(KeyEvent.KEY_RELEASED, event -> {
if (event.getCode() == KeyCode.ESCAPE && sb.length() > 0) {
resetSearch();
}
});
this.setOnKeyReleased(event -> {
if (Duration.between(instant, Instant.now()).toMillis() > IDLE_INTERVAL_MILLIS) {
resetSearch();
}
instant = Instant.now();
if (event.getCode() == KeyCode.DOWN || event.getCode() == KeyCode.UP || event.getCode() == KeyCode.TAB) {
return;
} else if (event.getCode() == KeyCode.BACK_SPACE && sb.length() > 0) {
sb.deleteCharAt(sb.length() - 1);
} else {
sb.append(event.getText().toLowerCase());
}
if (sb.length() == 0) {
return;
}
boolean found = false;
for (int i = 0; i < getItems().size(); i++) {
if (event.getCode() != KeyCode.BACK_SPACE && getItems().get(i).toString().toLowerCase().startsWith(sb.toString())) {
ListView listView = getListView();
listView.getSelectionModel().clearAndSelect(i);
scroll();
found = true;
break;
}
}
if (!found && sb.length() > 0)
sb.deleteCharAt(sb.length() - 1);
}
);
// add a focus listener such that if not in focus, reset the search process
this.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue) {
resetSearch();
} else {
scroll();
}
});
}
private void resetSearch() {
sb.setLength(0);
instant = Instant.now();
}
private void scroll() {
ListView listView = getListView();
int selectedIndex = listView.getSelectionModel().getSelectedIndex();
listView.scrollTo(selectedIndex == 0 ? selectedIndex : selectedIndex - 1);
}
private ListView getListView() {
return ((ComboBoxListViewSkin) this.getSkin()).getListView();
}
}
I improved this example in two ways.
All code is encapsulated in a class - nothing needs to be connected from the outside.
If the user doesn't show activity for some time, then the search string is reset. An alternative solution is how to reset the search: press the Backspace key, or make the ComboBox lose focus.

Easy way to dynamically invoke web services (without JDK or proxy classes)

In Python I can consume a web service so easily:
from suds.client import Client
client = Client('http://www.example.org/MyService/wsdl/myservice.wsdl') #create client
result = client.service.myWSMethod("Bubi", 15) #invoke method
print result #print the result returned by the WS method
I'd like to reach such a simple usage with Java.
With Axis or CXF you have to create a web service client, i.e. a package which reproduces all web service methods so that we can invoke them as if they where normal methods. Let's call it proxy classes; usually they are generated by wsdl2java tool.
Useful and user-friendly. But any time I add/modify a web service method and I want to use it in a client program I need to regenerate proxy classes.
So I found CXF DynamicClientFactory, this technique avoids the use of proxy classes:
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.endpoint.dynamic.DynamicClientFactory;
//...
//create client
DynamicClientFactory dcf = DynamicClientFactory.newInstance();
Client client = dcf.createClient("http://www.example.org/MyService/wsdl/myservice.wsdl");
//invoke method
Object[] res = client.invoke("myWSMethod", "Bubi");
//print the result
System.out.println("Response:\n" + res[0]);
But unfortunately it creates and compiles proxy classes runtime, hence requires JDK on the production machine. I have to avoid this, or at least I can't rely on it.
My question:
Is there another way to dinamically invoke any method of a web service in Java, without having a JDK at runtime and without generating "static" proxy classes? Maybe with a different library? Thanks!
I know this is a really old question but if you are still interested you could use soap-ws github project: https://github.com/reficio/soap-ws
Here you have a sample usage really simple:
Wsdl wsdl = Wsdl.parse("http://www.webservicex.net/CurrencyConvertor.asmx?WSDL");
SoapBuilder builder = wsdl.binding()
.localPart("CurrencyConvertorSoap")
.find();
SoapOperation operation = builder.operation()
.soapAction("http://www.webserviceX.NET/ConversionRate")
.find();
Request request = builder.buildInputMessage(operation)
SoapClient client = SoapClient.builder()
.endpointUrl("http://www.webservicex.net/CurrencyConvertor.asmx")
.build();
String response = client.post(request);
As you can see it is really simple.
With CXF 3.x this could be possible with StaxDataBinding. Follow below steps to get the basics. Of course, this could be enhanced to your needs.
Create StaxDataBinding something like below. Note below code can be enhanced to your sophistication.
class StaxDataBinding extends AbstractInterceptorProvidingDataBinding {
private XMLStreamDataReader xsrReader;
private XMLStreamDataWriter xswWriter;
public StaxDataBinding() {
super();
this.xsrReader = new XMLStreamDataReader();
this.xswWriter = new XMLStreamDataWriter();
inInterceptors.add(new StaxInEndingInterceptor(Phase.POST_INVOKE));
inFaultInterceptors.add(new StaxInEndingInterceptor(Phase.POST_INVOKE));
inInterceptors.add(RemoveStaxInEndingInterceptor.INSTANCE);
inFaultInterceptors.add(RemoveStaxInEndingInterceptor.INSTANCE);
}
static class RemoveStaxInEndingInterceptor
extends AbstractPhaseInterceptor<Message> {
static final RemoveStaxInEndingInterceptor INSTANCE = new RemoveStaxInEndingInterceptor();
public RemoveStaxInEndingInterceptor() {
super(Phase.PRE_INVOKE);
addBefore(StaxInEndingInterceptor.class.getName());
}
public void handleMessage(Message message) throws Fault {
message.getInterceptorChain().remove(StaxInEndingInterceptor.INSTANCE);
}
}
public void initialize(Service service) {
for (ServiceInfo serviceInfo : service.getServiceInfos()) {
SchemaCollection schemaCollection = serviceInfo.getXmlSchemaCollection();
if (schemaCollection.getXmlSchemas().length > 1) {
// Schemas are already populated.
continue;
}
new ServiceModelVisitor(serviceInfo) {
public void begin(MessagePartInfo part) {
if (part.getTypeQName() != null
|| part.getElementQName() != null) {
return;
}
part.setTypeQName(Constants.XSD_ANYTYPE);
}
}.walk();
}
}
#SuppressWarnings("unchecked")
public <T> DataReader<T> createReader(Class<T> cls) {
if (cls == XMLStreamReader.class) {
return (DataReader<T>) xsrReader;
}
else {
throw new UnsupportedOperationException(
"The type " + cls.getName() + " is not supported.");
}
}
public Class<?>[] getSupportedReaderFormats() {
return new Class[] { XMLStreamReader.class };
}
#SuppressWarnings("unchecked")
public <T> DataWriter<T> createWriter(Class<T> cls) {
if (cls == XMLStreamWriter.class) {
return (DataWriter<T>) xswWriter;
}
else {
throw new UnsupportedOperationException(
"The type " + cls.getName() + " is not supported.");
}
}
public Class<?>[] getSupportedWriterFormats() {
return new Class[] { XMLStreamWriter.class, Node.class };
}
public static class XMLStreamDataReader implements DataReader<XMLStreamReader> {
public Object read(MessagePartInfo part, XMLStreamReader input) {
return read(null, input, part.getTypeClass());
}
public Object read(QName name, XMLStreamReader input, Class<?> type) {
return input;
}
public Object read(XMLStreamReader reader) {
return reader;
}
public void setSchema(Schema s) {
}
public void setAttachments(Collection<Attachment> attachments) {
}
public void setProperty(String prop, Object value) {
}
}
public static class XMLStreamDataWriter implements DataWriter<XMLStreamWriter> {
private static final Logger LOG = LogUtils
.getL7dLogger(XMLStreamDataWriter.class);
public void write(Object obj, MessagePartInfo part, XMLStreamWriter writer) {
try {
if (!doWrite(obj, writer)) {
// WRITE YOUR LOGIC HOW you WANT TO HANDLE THE INPUT DATA
//BELOW CODE JUST CALLS toString() METHOD
if (part.isElement()) {
QName element = part.getElementQName();
writer.writeStartElement(element.getNamespaceURI(),
element.getLocalPart());
if (obj != null) {
writer.writeCharacters(obj.toString());
}
writer.writeEndElement();
}
}
}
catch (XMLStreamException e) {
throw new Fault("COULD_NOT_READ_XML_STREAM", LOG, e);
}
}
public void write(Object obj, XMLStreamWriter writer) {
try {
if (!doWrite(obj, writer)) {
throw new UnsupportedOperationException("Data types of "
+ obj.getClass() + " are not supported.");
}
}
catch (XMLStreamException e) {
throw new Fault("COULD_NOT_READ_XML_STREAM", LOG, e);
}
}
private boolean doWrite(Object obj, XMLStreamWriter writer)
throws XMLStreamException {
if (obj instanceof XMLStreamReader) {
XMLStreamReader xmlStreamReader = (XMLStreamReader) obj;
StaxUtils.copy(xmlStreamReader, writer);
xmlStreamReader.close();
return true;
}
else if (obj instanceof XMLStreamWriterCallback) {
((XMLStreamWriterCallback) obj).write(writer);
return true;
}
return false;
}
public void setSchema(Schema s) {
}
public void setAttachments(Collection<Attachment> attachments) {
}
public void setProperty(String key, Object value) {
}
}
}
Prepare your input to match the expected input, something like below
private Object[] prepareInput(BindingOperationInfo operInfo, String[] paramNames,
String[] paramValues) {
List<Object> inputs = new ArrayList<Object>();
List<MessagePartInfo> parts = operInfo.getInput().getMessageParts();
if (parts != null && parts.size() > 0) {
for (MessagePartInfo partInfo : parts) {
QName element = partInfo.getElementQName();
String localPart = element.getLocalPart();
// whatever your input data you need to match data value for given element
// below code assumes names are paramNames variable and value in paramValues
for (int i = 0; i < paramNames.length; i++) {
if (paramNames[i].equals(localPart)) {
inputs.add(findParamValue(paramNames, paramValues, localPart));
}
}
}
}
return inputs.toArray();
}
Now set the proper data binding and pass the data
Bus bus = CXFBusFactory.getThreadDefaultBus();
WSDLServiceFactory sf = new WSDLServiceFactory(bus, wsdl);
sf.setAllowElementRefs(false);
Service svc = sf.create();
Client client = new ClientImpl(bus, svc, null,
SimpleEndpointImplFactory.getSingleton());
StaxDataBinding databinding = new StaxDataBinding();
svc.setDataBinding(databinding);
bus.getFeatures().add(new StaxDataBindingFeature());
BindingOperationInfo operInfo = ...//find the operation you need (see below)
Object[] inputs = prepareInput(operInfo, paramNames, paramValues);
client.invoke("operationname", inputs);
If needed you can match operation name something like below
private BindingOperationInfo findBindingOperation(Service service,
String operationName) {
for (ServiceInfo serviceInfo : service.getServiceInfos()) {
Collection<BindingInfo> bindingInfos = serviceInfo.getBindings();
for (BindingInfo bindingInfo : bindingInfos) {
Collection<BindingOperationInfo> operInfos = bindingInfo.getOperations();
for (BindingOperationInfo operInfo : operInfos) {
if (operInfo.getName().getLocalPart().equals(operationName)) {
if (operInfo.isUnwrappedCapable()) {
return operInfo.getUnwrappedOperation();
}
return operInfo;
}
}
}
}
return null;
}

Silverlight Async Method Chaining (Possible gotchas?)

I am working on a 'proof of concept' Silverlight 4 project and am learning the way of THE ASYNC. I have stopped fighting the urge to implement some pseudo-synchronous smoke and mirrors technique. I am going to learn to stop worrying and love THE ASYNC.
Most of the time I just use a BusyIndicator while async methods are running and all is good but I have run into a few situations where I need to call methods sequentially. I put together this example and it works. But in my experience... if it works... there is something wrong with it.
When is this going to blow up in my face or steal my wife or date one of my daughters?
Is there a better way to do this?
The Code:
public class CustomPage : Page
{
static readonly object _AsyncMethodChain_Lock = new object();
private Dictionary<Action<object>, string> _AsyncMethodChain = new Dictionary<Action<object>, string>();
public Dictionary<Action<object>, string> AsyncMethodChain
{
get { lock (_AsyncMethodChain_Lock) { return this._AsyncMethodChain; } }
set { lock (_AsyncMethodChain_Lock) { this._AsyncMethodChain = value; } }
}
private void CustomPage_Loaded(object sender, RoutedEventArgs e)
{
if (!System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
{
var user = this.SecurityProvider.UserObject as TimeKeeper.UserServiceReference.User;
if (user == null)
return;
this.AsyncMethodChain.Add(
data =>
{
var userServiceClient = new UserServiceClient();
userServiceClient.GetCompleted +=
(send, arg) =>
{
var userViewSource = this.Resources["userViewSource"] as CollectionViewSource;
userViewSource.Source = new List<UserServiceReference.User>(new UserServiceReference.User[1] { arg.Result });
userViewSource.View.MoveCurrentToPosition(0);
this.AsyncMethodChain.ExecuteNext(arg.Result.UserID, this.BusyIndicator);
};
userServiceClient.GetAsync(user.UserID);
},
"Loading user..."
);
this.AsyncMethodChain.Add(
data =>
{
var userID = (int)data;
var timeLogServiceClient = new TimeLogServiceClient();
timeLogServiceClient.FindByUserIDCompleted +=
(send, arg) =>
{
var timeLogViewSource = this.Resources["timeLogViewSource"] as CollectionViewSource;
timeLogViewSource.Source = arg.Result;
this.AsyncMethodChain.ExecuteNext(null, this.BusyIndicator);
};
timeLogServiceClient.FindByUserIDAsync(userID);
},
"Loading time logs..."
);
this.AsyncMethodChain.ExecuteNext(null, this.BusyIndicator);
}
}
}
public static class Extensions
{
public static void ExecuteNext(this Dictionary<Action<object>, string> methods, object data, BusyIndicator busyIndicator)
{
if (methods.Count <= 0)
{
busyIndicator.BusyContent = "";
busyIndicator.IsBusy = false;
return;
}
else
{
var method = methods.Keys.ToList<Action<object>>()[0];
busyIndicator.BusyContent = methods[method];
busyIndicator.IsBusy = true;
}
methods.ExecuteNext(data);
}
public static void ExecuteNext(this Dictionary<Action<object>, string> methods, object data)
{
var method = methods.Keys.ToList<Action<object>>()[0];
methods.Remove(method);
method(data);
}
}
What you have sine looks pretty good, but if you are still worried about the callsequence i would sugest that you create a new method in your webservice which will call the other four in whatever sequence you need them to and call this new method from your silverlight application.
I dont see the need for you to do that as your current implemenation is pretty good as well.

Resources