I have a String that contains an xml like this:
<root>
<type>
<element>
<thing>
<otherthing>...</otherthing>
</thing>
</element>
</type>
<type>
<element>
<thing>
<otherthing>...</otherthing>
</thing>
</element>
</type>
<type>
<element>
<thing>
<otherthing>...</otherthing>
</thing>
</element>
</type>
</root>
I need a treenode in my treeview for each indentation so I can expand and contract it when I want cause there is so much information in each node, how can I do it?
The result should be like this:
ROOT
---+type
--------+element
----------------+thing
----------------------+otherthing
---+type
--------+element
----------------+thing
----------------------+otherthing
---+type
--------+element
----------------+thing
----------------------+otherthing
Thank you!
Use a xml parser to parse the data, create a TreeItem representing it and use a TreeView to display the data.
Example:
private static class TreeItemCreationContentHandler extends DefaultHandler {
private TreeItem<String> item = new TreeItem<>();
#Override
public void endElement(String uri, String localName, String qName) throws SAXException {
// finish this node by going back to the parent
this.item = this.item.getParent();
}
#Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
// start a new node and use it as the current item
TreeItem<String> item = new TreeItem<>(qName);
this.item.getChildren().add(item);
this.item = item;
}
#Override
public void characters(char[] ch, int start, int length) throws SAXException {
String s = String.valueOf(ch, start, length).trim();
if (!s.isEmpty()) {
// add text content as new child
this.item.getChildren().add(new TreeItem<>(s));
}
}
}
public static TreeItem<String> readData(File file) throws SAXException, ParserConfigurationException, IOException {
SAXParserFactory parserFactory = SAXParserFactory.newInstance();
SAXParser parser = parserFactory.newSAXParser();
XMLReader reader = parser.getXMLReader();
TreeItemCreationContentHandler contentHandler = new TreeItemCreationContentHandler();
// parse file using the content handler to create a TreeItem representation
reader.setContentHandler(contentHandler);
reader.parse(file.toURI().toString());
// use first child as root (the TreeItem initially created does not contain data from the file)
TreeItem<String> item = contentHandler.item.getChildren().get(0);
contentHandler.item.getChildren().clear();
return item;
}
// display data for file "data/tree.xml" in TreeView
TreeItem<String> root = readData(new File("data/tree.xml"));
TreeView<String> treeView = new TreeView<>(root);
Related
I'm using a FilteredList for my ListView to enable searching. The problem is that FilteredList does not allow mutating the data in any way, it only responds to changes in underlying ObservableList.
Also, it is declared final, so I can't simply extend it to forward the edit requests to the source.
So, how can I use it in an Editable ListView?
Here is the code to reproduce the problem
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
//problematic code
var observableList = FXCollections.observableArrayList("name", "name 2", "name 3");
FilteredList<String> filteredList = new FilteredList<>(observableList);
var list = new ListView<>(filteredList);
list.setEditable(true);
list.setCellFactory(TextFieldListCell.forListView());
//boilerplate code
VBox wrapper = new VBox(list);
primaryStage.setScene(new Scene(wrapper));
primaryStage.show();
}
}
Edit: added an minimal reproducible example
The problem - as you noticed - is that none of the concrete implementations of TransformationList (Sorted/FilteredList) is modifiable. So the default commit handler fails (with UnsupportedOperationException) while trying to set the newValue:
private EventHandler<ListView.EditEvent<T>> DEFAULT_EDIT_COMMIT_HANDLER = t -> {
int index = t.getIndex();
List<T> list = getItems();
if (index < 0 || index >= list.size()) return;
list.set(index, t.getNewValue());
};
The way out is a custom commit handler. Its implementation depends on context, it can
set a new item in the underlying source list
modify a property of the item
Code snippet for setting an item:
// monolithic items
ObservableList<String> data = FXCollections.observableArrayList("afirst", "abString", "other");
FilteredList<String> filteredData = new FilteredList<>(data);
filteredData.setPredicate(text -> text.contains("a"));
// set up an editable listView
ListView<String> list = new ListView<>(filteredData);
list.setEditable(true);
list.setCellFactory(TextFieldListCell.forListView());
// commitHandler resetting the underlying data element
list.setOnEditCommit(v -> {
ObservableList<String> items = list.getItems();
int index = v.getIndex();
if (items instanceof TransformationList<?, ?>) {
TransformationList transformed = (TransformationList) items;
items = transformed.getSource();
index = transformed.getSourceIndex(index);
}
items.set(index, v.getNewValue());
});
Code snippet for changing a property of an item:
// items with properties
ObservableList<MenuItem> data = FXCollections.observableArrayList(
new MenuItem("afirst"), new MenuItem("abString"), new MenuItem("other"));
FilteredList<MenuItem> filteredData = new FilteredList<>(data);
// filter on text property
filteredData.setPredicate(menuItem -> menuItem.getText().contains("a"));
// set up an editable listView
ListView<MenuItem> list = new ListView<>(filteredData);
list.setEditable(true);
// converter for use in TextFieldListCell
StringConverter<MenuItem> converter = new StringConverter<>() {
#Override
public String toString(MenuItem menuItem) {
return menuItem != null ? menuItem.getText() : null;
}
#Override
public MenuItem fromString(String text) {
return new MenuItem(text);
}
};
list.setCellFactory(TextFieldListCell.forListView(converter));
// commitHandler changing a property of the item
list.setOnEditCommit(v -> {
ObservableList<MenuItem> items = list.getItems();
MenuItem column = items.get(v.getIndex());
MenuItem standIn = v.getNewValue();
column.setText(standIn.getText());
});
I have this code for web development:
protected internal class MyEventHandler : iText.Kernel.Events.IEventHandler
{
public virtual void HandleEvent(iText.Kernel.Events.Event #event)
{
iText.Kernel.Events.PdfDocumentEvent docEvent =
(iText.Kernel.Events.PdfDocumentEvent)#event;
PdfDocument pdfDoc = docEvent.GetDocument();
}
public void onStartPage(
iText.Kernel.Pdf.PdfWriter writer, iText.Layout.Document document)
{
// paragragrap for start pages
…
}
}
…
MyEventHandler StartPage = new MyEventHandler();
pdf.AddEventHandler(iText.Kernel.Events.PdfDocumentEvent.START_PAGE, new MyEventHandler());
StartPage.onStartPage(writer, document);
And other code that adds more to the page.
It only makes the header for the first page.
You are mixing event handling from different iText versions.
In iText 5 you used to implement IPdfPageEvent, often by extending PdfPageEventHelper, and here you had separate methods like
void OnStartPage(PdfWriter writer, Document document)
void OnEndPage(PdfWriter writer, Document document)
etc.
In iText 7 you implement IEventHandler which has only a single method,
void HandleEvent(Event #event)
You either register your event handler only for one event type and, consequentially, know in HandleEvent that the right event arrived, or you determine based on the event type (#event.GetEventType()) which type of event you got and execute the appropriate code.
An example event listener changing page rotation
E.g. in this example the event handler is registered for page starts only
C07E01_EventHandlers.PageRotationEventHandler eventHandler = new PageRotationEventHandler();
pdf.AddEventHandler(PdfDocumentEvent.START_PAGE, eventHandler);
and in its event handling method acts accordingly
public virtual void HandleEvent(Event #event) {
PdfDocumentEvent docEvent = (PdfDocumentEvent)#event;
docEvent.GetPage().Put(PdfName.Rotate, this.rotation);
}
The full example class:
public class C07E01_EventHandlers {
public const String DEST = "../../../results/chapter07/jekyll_hyde_page_orientation.pdf";
public static readonly PdfNumber PORTRAIT = new PdfNumber(0);
public static readonly PdfNumber LANDSCAPE = new PdfNumber(90);
public static readonly PdfNumber INVERTEDPORTRAIT = new PdfNumber(180);
public static readonly PdfNumber SEASCAPE = new PdfNumber(270);
public static void Main(String[] args) {
FileInfo file = new FileInfo(DEST);
file.Directory.Create();
new C07E01_EventHandlers().CreatePdf(DEST);
}
public virtual void CreatePdf(String dest) {
PdfDocument pdf = new PdfDocument(new PdfWriter(dest));
pdf.GetCatalog().SetPageLayout(PdfName.TwoColumnLeft);
C07E01_EventHandlers.PageRotationEventHandler eventHandler = new PageRotationEventHandler();
pdf.AddEventHandler(PdfDocumentEvent.START_PAGE, eventHandler);
Document document = new Document(pdf, PageSize.A8);
document.Add(new Paragraph("Dr. Jekyll"));
eventHandler.SetRotation(INVERTEDPORTRAIT);
document.Add(new AreaBreak());
document.Add(new Paragraph("Mr. Hyde"));
eventHandler.SetRotation(LANDSCAPE);
document.Add(new AreaBreak());
document.Add(new Paragraph("Dr. Jekyll"));
eventHandler.SetRotation(SEASCAPE);
document.Add(new AreaBreak());
document.Add(new Paragraph("Mr. Hyde"));
document.Close();
}
protected internal class PageRotationEventHandler : IEventHandler {
protected internal PdfNumber rotation = C07E01_EventHandlers.PORTRAIT;
public virtual void SetRotation(PdfNumber orientation) {
this.rotation = orientation;
}
public virtual void HandleEvent(Event #event) {
PdfDocumentEvent docEvent = (PdfDocumentEvent)#event;
docEvent.GetPage().Put(PdfName.Rotate, this.rotation);
}
internal PageRotationEventHandler() {
}
}
}
An example page listener adding headers and footers
In comments you asked for an example for header and not rotation a page. For that look e.g. at the iText 7 Jump-Start Tutorial chapter 3 (Using renderers and event handlers) example C03E03_UFO (Java version / C# version) in which an event listener is used to add a background, a watermark, a header, and a footer:
public class C03E03_UFO {
internal static PdfFont helvetica = null;
internal static PdfFont helveticaBold = null;
public static void Main(String[] args) {
helvetica = PdfFontFactory.CreateFont(StandardFonts.HELVETICA);
helveticaBold = PdfFontFactory.CreateFont(StandardFonts.HELVETICA_BOLD);
...
}
protected internal virtual void CreatePdf(String dest) {
//Initialize PDF document
PdfDocument pdf = new PdfDocument(new PdfWriter(dest));
pdf.AddEventHandler(PdfDocumentEvent.END_PAGE, new C03E03_UFO.MyEventHandler(this));
...
}
...
protected internal class MyEventHandler : IEventHandler {
public virtual void HandleEvent(Event #event) {
PdfDocumentEvent docEvent = (PdfDocumentEvent)#event;
PdfDocument pdfDoc = docEvent.GetDocument();
PdfPage page = docEvent.GetPage();
int pageNumber = pdfDoc.GetPageNumber(page);
Rectangle pageSize = page.GetPageSize();
PdfCanvas pdfCanvas = new PdfCanvas(page.NewContentStreamBefore(), page.GetResources(), pdfDoc);
//Set background
Color limeColor = new DeviceCmyk(0.208f, 0, 0.584f, 0);
Color blueColor = new DeviceCmyk(0.445f, 0.0546f, 0, 0.0667f);
pdfCanvas.SaveState()
.SetFillColor(pageNumber % 2 == 1 ? limeColor : blueColor)
.Rectangle(pageSize.GetLeft(), pageSize.GetBottom(), pageSize.GetWidth(), pageSize.GetHeight())
.Fill()
.RestoreState();
//Add header and footer
pdfCanvas.BeginText()
.SetFontAndSize(C03E03_UFO.helvetica, 9)
.MoveText(pageSize.GetWidth() / 2 - 60, pageSize.GetTop() - 20)
.ShowText("THE TRUTH IS OUT THERE")
.MoveText(60, -pageSize.GetTop() + 30)
.ShowText(pageNumber.ToString())
.EndText();
//Add watermark
iText.Layout.Canvas canvas = new iText.Layout.Canvas(pdfCanvas, pdfDoc, page.GetPageSize());
canvas.SetFontColor(ColorConstants.WHITE);
canvas.SetProperty(Property.FONT_SIZE, UnitValue.CreatePointValue(60));
canvas.SetProperty(Property.FONT, C03E03_UFO.helveticaBold);
canvas.ShowTextAligned(new Paragraph("CONFIDENTIAL"), 298, 421, pdfDoc.GetPageNumber(page), TextAlignment.
CENTER, VerticalAlignment.MIDDLE, 45);
pdfCanvas.Release();
}
internal MyEventHandler(C03E03_UFO _enclosing) {
this._enclosing = _enclosing;
}
private readonly C03E03_UFO _enclosing;
}
}
Strictly speaking that example listens to PdfDocumentEvent.END_PAGE instead of PdfDocumentEvent.START_PAGE. That difference shouldn't hurt too much, though.
An ObservableList obList has a listener, upon addition it updates the TreeView. As new string "music" is added to obList, new item is created and put in the tree view. The item renders as "{ [music] added at 0 }" instead of expected "music".
TreeItem<String> root = new TreeItem();
root.setExpanded(true);
TreeView<String> treeView = new TreeView(root);
treeView.getChildren().add(new TreeItem<String>("cat")); // normal behaviour
obList = FXCollections.observableArrayList();
obList.addListener(new ListChangeListener<String>() {
#Override
public void onChanged(ListChangeListener.Change<? extends String> c) {
while (c.next()) {
if (c.wasAdded()) {
TreeItem<String> temp = new TreeItem(c);
tree.getRoot().getChildren().add(temp);
}
}
});
obList.add("music");
It seems that variable c contains string and extra information. What is going on and what should I do?
If you didn't use the raw type, the compiler would have complained about the issue.
You set the value to the ListChangeListener.Change object instead of a String in the following line. Using the raw type on the right hand side removes the type check that would have resulted in a compile time error.
TreeItem<String> temp = new TreeItem(c);
Instead iterate through the list of added items and add a TreeItem for all of them:
while (c.next()) {
if (c.wasAdded()) {
for (String element : c.getAddedSubList()) {
TreeItem<String> temp = new TreeItem<>(element);
tree.getRoot().getChildren().add(temp);
}
}
}
Ok, I fixed it with:
obList.getAddedSubList().forEach(l ->
TreeItem<String> temp = new TreeItem(l);
tree.getRoot().getChildren().add(temp);
});
I am busy with a book reader project for Android. I am using SQLite database. To get certain entries from certain chapters depending on switch/case, I am using an intent putextra/getextra which works just fine to get just entries and populate the standard array adapter. However, I need the items numbered.
I have created a custom adapter (EntryAdapter) to show the line numbers as follows:
entry
entry
entry
and so on... in the list view
The problem is that the customer adapter is passes the entire chapter to each list item view over and over. Without the customer adapter (and using a standard array adapter with the standard simple_list_item) it passes one line per list item as it is supposed to, however, I need the lines numbered... thus the custom adapter... How do I get the entries to pass properly... only one entry per list item while still having the lines numbered? Any help would be appreciated.
Here is a sample of the Activity that populates the listview...
public class ChapterActivity extends AppCompatActivity{
private ListView mListView;
private EntryAdapter mAdapter;
static List<String> mChapterEntries;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chapter_entries);
this.mListView = (ListView) findViewById(R.id.listView);
Integer chapterSelectedOne = getIntent().getIntExtra("chapter", 0);
final DatabaseAccess databaseAccess = DatabaseAccess.getInstance(this);
switch (chapterSelectedOne) {
case 0:
databaseAccess.open();
mChapterEntries = databaseAccess.getEntriesChapterOne();
databaseAccess.close();
mAdapter = new EntryAdapter(this, R.layout.entry_list_item, mChapterEntries);
this.mListView.setAdapter(mAdapter);
break;
case 1:
databaseAccess.open();
mChapterEntries = databaseAccess.getEntriesChapterTwo();
databaseAccess.close();
mAdapter = new EntryAdapter(this, R.layout.entry_list_item, mChapterEntries);
this.mListView.setAdapter(mAdapter);
break;
case 2:
databaseAccess.open();
mChapterEntries = databaseAccess.getEntriesChapterThree();
databaseAccess.close();
mAdapter = new EntryAdapter(this, R.layout.entry_list_item, mChapterEntries);
this.mListView.setAdapter(mAdapter);
break;
case 3:
databaseAccess.open();
mChapterEntries = databaseAccess.getEntriesChapterFour();
databaseAccess.close();
mAdapter = new EntryAdapter(this, R.layout.entry_list_item, mChapterEntries);
this.mListView.setAdapter(mAdapter);
break;
This is the custom adapter "EntryAdapter"....
class EntryAdapter extends ArrayAdapter<String> {
public EntryAdapter(Context context, int layout, List<String>
mChapterEntries) {
super(context, R.layout.entry_list_item, mChapterEntries);
}
#NonNull
#Override
public View getView(int position, #Nullable View convertView, #NonNull
ViewGroup parent) {
View listItem = convertView;
int pos = position +1;
if(listItem == null)
listItem =
LayoutInflater.from(getContext()).inflate(R.layout.entry_list_item,
parent, false);
TextView entryTextView = (TextView)
listItem.findViewById(R.id.chapterEntries);
verseTextView.setText(String.valueOf(pos)+ mChapterEntries);
return listItem;
}
}
I fixed it by using by using this:
verseTextView.setText(String.valueOf(pos)+ mChapterEntries.get(position));
instead of this:
verseTextView.setText(String.valueOf(pos)+ mChapterEntries);
Hope that helps someone else.
I have 3 ListFragments being handled by a viewPager (managed by a FragmentAdapter) - they work perfectly. Now when the user clicks an item in ListFragment #1, a new Fragment should open with the details. It's behaving strangely in the following manner:
Only clicking a list item twice opens the DetailFragment, yet debugging shows the first click indeed goes into the DetailFragment, but doesn't show the view (the view still shows the current ListFragment).
After clicking the 2nd time, the DetailFragment does show it's layout, but not the elements within it (like TextView, etc).
If the user 'accidently' swipes the screen when DetailFragment is showing, the viewPager sets it in place of the 2nd ListFragment! Only when pressing back on the DetailFragment view will 'reset' the viewPager to it's correct ListFragment. Of course if the user swipes when in a DetailFragment, the next ListFragment of the viewPager should appear, and the DetailFragment should be removed.
Thanks for any tips muddling through Android's odd world of fragments and views :)
public class PlanetFragment extends ListFragment{
LayoutInflater inflater;
ListView list;
ArrayList<HashMap<String, String>> planetListArray;
HashMap<String, String> planetMap;
Activity activity;
Context context;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.planets_tab_layout, container, false);
inflater=(LayoutInflater)getLayoutInflater(savedInstanceState);
activity = getActivity();
context = PlanetFragment.this.getActivity();
String dbTableName = "Table_Planets";
SQLiteHelper info = new SQLiteHelper(getActivity().getBaseContext());
info.open();
ArrayList<HashMap<String, String>> datafromSQL = info.getData(dbTableName);
if(!datafromSQL.isEmpty()){
planetListArray = new ArrayList<HashMap<String, String>>();
for (int i = 0; i<datafromSQL.size(); i++){
planetMap = new HashMap<String, String>();
planetMap.put(PLANET_ID, datafromSQL.get(i).get(KEY_PLANET_ID));
planetMap.put(ZODIAC_ID, datafromSQL.get(i).get(KEY_ZODIAC_ID));
planetMap.put(DEGREES, datafromSQL.get(i).get(KEY_DEGREES));
planetMap.put(CONTENT, datafromSQL.get(i).get(KEY_CONTENT));
planetListArray.add(planetMap);
}
info.close();
}
list = (ListView) v.findViewById(android.R.id.list);
PlanetAdapter adapter=new PlanetAdapter(getActivity(), R.layout.planets_row, planetListArray);
list.setAdapter(adapter);
return v;
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
//the dividers
getListView().setDivider(getResources().getDrawable(R.drawable.purplebartop));
}
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
HashMap<String, String> item = planetListArray.get(position);
Bundle bundle = new Bundle();
bundle.putSerializable("itemMap", item);
bundle.putInt("position", position);
Fragment frag = DetailFragment.newInstance();
frag.setArguments(bundle);
if (frag != null) {
getActivity().getSupportFragmentManager()
.beginTransaction()
.replace(R.id.pager, frag, "frag")
.addToBackStack("frag")
.commit();
}
}
}
public class DetailFragment extends Fragment{
Context context;
Activity activity;
TextView planetName;
public static android.support.v4.app.Fragment newInstance() {
DetailFragment f = new DetailFragment();
return f;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
inflater=(LayoutInflater)getLayoutInflater(savedInstanceState);
View v = inflater.inflate(R.layout.dialog_details, container, false);
activity = getActivity();
context = DetailFragment.this.getActivity();
planetName = (TextView)v.findViewById(R.id.planetNameExpanded);
planetName.setText("planetX");
return v;
}
}
EDIT:
Instead of getActivity().getSupportFragmentManager() I have also tried getChildFragmentManager() but it always gives the error: The method getChildFragmentManager() is undefined for the type PlanetFragment.
When you click on a list item, you are indeed constructing a new details fragment and telling the fragment manager to replace the tag "frag" with that fragment. However, you are not telling the view pager to switch over to that fragment.
Since you already have a back-pointer to your activity, you could use findViewById to find your view pager, and then call viewPager.setCurrentItem.
I think you might be asking for trouble by constructing a new details fragment inside of the list fragment. When you use a FragmentPagerAdapter, the adapter usually constructs the fragments. I would have implemented this by letting the adapter make the fragments, and then in your onListItemClick find the existing details fragment and call a method on it to configure it with the new data. But maybe just the setCurrentItem will fix your problem.
EDIT
First, I would write your FragmentPagerAdapter so you can use getItem to fetch the existing fragment, without creating a new one each time.
public class PlanetFragmentAdapter extends FragmentPagerAdapter {
private Fragment [] fragments = new Fragments[3];
public PlanetFragmentAdapter(FragmentManager fm) {
super(fm);
}
#Override
public int getCount() {
return 3;
}
#Override
public Fragment getItem(int position) {
Fragment fragment = fragments[position];
if (fragment == null) {
switch (position) {
case 0:
fragment = new PlanetFragment();
break;
case 1:
fragment = new DetailFragment();
break;
case 2:
fragment = new MysteryFragment();
break;
}
fragments[position] = fragment;
}
return fragment;
}
}
Also add functions in your activity to work with your fragments:
public void setPage(int position) {
viewPager.setCurrentItem(position);
}
public DetailFragment getDetailFragment() {
return (DetailFragment) viewPager.getItem(1); // now it doesn't create a new instance
// you could also use getSupportFragmentManager().findFragmentById() here
}
Now when you click on an item in your list fragment, you can get the existing detail fragment, configure it, and set the ViewPager to show the detail fragment.
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
HashMap<String, String> item = planetListArray.get(position);
Bundle bundle = new Bundle();
bundle.putSerializable("itemMap", item);
bundle.putInt("position", position);
PlanetActivity pa = (PlanetActivity) activity;
DetailFragment frag = pa.getDetailFragment();
frag.setArguments(bundle);
pa.setCurrentItem(1);
}