I have a Treeview with menu content, which is working. I am just not sure how can I implement more root menu to add?
Because this code only shows the "File" menu and all of submenus, but not the other roots.
-Also I would like to ask how could I make these submenus to act like links and create mouselisteners to them? Where is the right place to take the listeners?
The code is the following:
TreeItem<String> treeItemRoot1 = new TreeItem<> ("File");
TreeItem<String> treeItemRoot2 = new TreeItem<> ("Edit");
TreeItem<String> treeItemRoot3 = new TreeItem<> ("View");
TreeItem<String> treeItemRoot4 = new TreeItem<> ("Tools");
TreeItem<String> treeItemRoot5 = new TreeItem<> ("Help");
TreeItem<String> nodeItemA = new TreeItem<>("Item A");
TreeItem<String> nodeItemB = new TreeItem<>("Item B");
TreeItem<String> nodeItemC = new TreeItem<>("Item C");
treeItemRoot1.getChildren().addAll(nodeItemA, nodeItemB, nodeItemC);
TreeView<String> treeView = new TreeView<>(treeItemRoot1);
StackPane.getChildren().add(treeView);
The first part of your question is answered here: Set two root nodes for TreeView
For the second part, it depends on exactly what functionality you want. If you want to respond to a change in the selected item in the tree (this would include the user selecting either with the mouse or by using the keyboard), so can add a listener to the tree's selected item:
treeView.getSelectionModel().selectedItemProperty().addListener((obs, oldItem, newItem) -> {
if (newItem == treeItemRoot1) {
// "file" selected...
} else if (newItem == treeItemRoot2) {
// edit selected
} // etc...
});
If you genuinely want a mouse listener, you need to add a listener to the cell. To do this, use a cell factory:
treeView.setCellFactory(tv -> {
TreeCell<String> cell = new TreeCell<>();
cell.textProperty().bind(cell.itemProperty());
cell.setOnMousePressed(event -> {
TreeItem<String> item = cell.getTreeItem();
if (item == treeItemRoot1) {
// "file" clicked...
} else if (item == treeItemRoot2) {
// etc...
}
}
return cell ;
});
You can probably find ways to organize the code a little more cleanly, and avoid the big if-else construct in either case.
Related
I try to run a function when the user click on the delete button on the keyboard when he try to modify a Textfield.
How can I do that ?
Yes it is possible, however it requires subclassing UITextField and creating your own UIViewRepresentable
This answer is based on the fantastic work done by Costantino Pistagna in his medium article but we need to do a little more work.
Firstly we need to create our subclass of UITextField, this should also conform to the UITextFieldDelegate protocol.
class WrappableTextField: UITextField, UITextFieldDelegate {
var textFieldChangedHandler: ((String)->Void)?
var onCommitHandler: (()->Void)?
var deleteHandler: (() -> Void)?
override func deleteBackward() {
super.deleteBackward()
deleteHandler?()
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if let nextField = textField.superview?.superview?.viewWithTag(textField.tag + 1) as? UITextField {
nextField.becomeFirstResponder()
} else {
textField.resignFirstResponder()
}
return false
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if let currentValue = textField.text as NSString? {
let proposedValue = currentValue.replacingCharacters(in: range, with: string)
textFieldChangedHandler?(proposedValue as String)
}
return true
}
func textFieldDidEndEditing(_ textField: UITextField) {
onCommitHandler?()
}
}
Because we are creating our own implementation of a TextField we need three functions that we can use for callbacks.
textFieldChangeHandler this will be called when the text property updates and allows us to change the state value associated with our Textfield.
onCommitHandler this will be called when we have finished editing our TextField
deleteHandler this will be called when we perform he delete action.
The code above shows how these are used. The part that you are particularly interested in is the override func deleteBackward(), by overriding this we are able to hook into when the delete button is pressed and perform an action on it. Depending on your use case, you may want the deleteHandler to be called before you call the super.
Next we need to create our UIViewRepresentable.
struct MyTextField: UIViewRepresentable {
private let tmpView = WrappableTextField()
//var exposed to SwiftUI object init
var tag:Int = 0
var placeholder:String?
var changeHandler:((String)->Void)?
var onCommitHandler:(()->Void)?
var deleteHandler: (()->Void)?
func makeUIView(context: UIViewRepresentableContext<MyTextField>) -> WrappableTextField {
tmpView.tag = tag
tmpView.delegate = tmpView
tmpView.placeholder = placeholder
tmpView.onCommitHandler = onCommitHandler
tmpView.textFieldChangedHandler = changeHandler
tmpView.deleteHandler = deleteHandler
return tmpView
}
func updateUIView(_ uiView: WrappableTextField, context: UIViewRepresentableContext<MyTextField>) {
uiView.setContentHuggingPriority(.defaultHigh, for: .vertical)
uiView.setContentHuggingPriority(.defaultLow, for: .horizontal)
}
}
This is where we create our SwiftUI version of our WrappableTextField. We create our WrappableTextField and its properties. In the makeUIView function we assign these properties. Finally in the updateUIView we set the content hugging properties, but you may choose not to do that, it really depends on your use case.
Finally we can create a small working example.
struct ContentView: View {
#State var text = ""
var body: some View {
MyTextField(tag: 0, placeholder: "Enter your name here", changeHandler: { text in
// update the state's value of text
self.text = text
}, onCommitHandler: {
// do something when the editing finishes
print("Editing ended")
}, deleteHandler: {
// do something here when you press delete
print("Delete pressed")
})
}
}
I have a tableView in a View Controller with delegate and datasource wired up through the IB. I'm using the tableView for multi selection. In my didSelectRowAt IndexPath, selection lets the user mark a relationship between two Core Data entities:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
let wageClassObject = self.wageClasses[indexPath.row]
let wageClassStatus = wageClassObject.value(forKey: "checked") as? Bool
wageClassObject.setValue(!wageClassStatus!, forKey:"checked")
if (wageClassStatus)! {
proposalToEdit?.addToWageClasses(wageClassObject)
}
else {
proposalToEdit?.removeFromWageClasses(wageClassObject)
}
}
When the user loads the view the first time, selecting a row has no effect. When the user loads the view the second time, the add and remove functions work great.
I've tried every solution suggested in this thread but no dice. I can't find a case quite like mine, either, where the issue is that it doesn't work the first time the view loads.
Suggestions greatly appreciated!
Edited to add: viewDidLoad and Core Data code
override func viewDidLoad() {
loadProposalData()
tableView.reloadData()
super.viewDidLoad()
proposalNameField.delegate = self
pensionField.delegate = self
percentField.delegate = self
if let topItem = self.navigationController?.navigationBar.topItem {
topItem.backBarButtonItem = UIBarButtonItem(title: "", style: UIBarButtonItemStyle.plain, target: nil, action: nil)
}
if wageClasses != nil {
getWageClasses()
print("\(wageClasses)")
}
}
func getWageClasses(){
let fetchRequest: NSFetchRequest<WageClass> = WageClass.fetchRequest()
do {
try self.wageClasses = context.fetch(fetchRequest)
} catch {
// handle error
}
}
#IBAction func calculateButtonTapped(_ sender: UIButton) {
var proposal: Proposal!
if proposalToEdit == nil {
proposal = Proposal(context: context)
} else {
proposal = proposalToEdit
}
proposal.dateCreated = currentDateTime as NSDate?
if let proposalName = proposalNameField.text {
proposal.proposalName = proposalName
}
if let pensionContribution = pensionField.text {
proposal.pensionContribution = (pensionContribution as NSString).doubleValue
}
if let percentIncrease = percentField.text {
proposal.percentIncrease = (percentIncrease as NSString).doubleValue
}
/// if let setting the checked value?
adD.saveContext()
_ = navigationController?.popViewController(animated: true)
}
I am not using viewDidAppear.
A little more about the Core Data. I have two entities, proposal and wageClass. Both have a to-many relationship to each other. On this viewController, the user can edit the proposal through three textfields (name, percent increase, pension amount) and then select related wageClasses from the tableView.
I want the user to be able to input the proposal fields and select the related wageClasses the first time they click the Add button in the nav bar on the previous viewController. What's happening is that the user clicks add, inputs the data to the text fields, selects the wage classes, and hits calculate. The text fields save and the labels update with the programmed calculations, but the wageClasses data remain nil. If the user selects the proposal again (that's what I mean by loads the view a second time above) then the user can add the wageClasses and all the calculations run correctly. But not before that second opening of the view.
screenshot of the view
You code uses proposalToEdit instance variable:
if (wageClassStatus)! {
proposalToEdit?.addToWageClasses(wageClassObject)
}
else {
proposalToEdit?.removeFromWageClasses(wageClassObject)
}
Which is basically nil here, it must be initialized before calling this functions, thus making this calls not executed (can't be done on nil object). You insert it to the context for the first time in calculateButtonTapped:
if proposalToEdit == nil {
proposal = Proposal(context: context)
}
if you want it to work first time (i.e. before first click on 'calculate'), add above code somewhere in viewDidLoad, like this:
override func viewDidLoad() {
loadProposalData()
if proposalToEdit == nil {
proposal = Proposal(context: context)
}
If above doesn't work I suggest you add a link to the repository to help debug this code, I cannot find more flaws in here, but there are no details in add and remove implementations, as well as how your core data stack works.
If anyone is stuck on the same problem, the key is to create the core data object before you add a relationship to another object. The above code doesn't work because I was trying to add relationships to objects that didn't yet exist.
My solution was to separate the operations in two view controllers but I would love to know more about how such can be achieved in one view controller.
I have a list of people that I want to display with checkboxes next to their names. When an CheckBoxElement (person) is checked or unchecked, I need to handle the event.
List<CheckboxElement> cbPersonElements = new List<CheckboxElement> ();
CheckboxElement tmpCheckbox = new CheckboxElement ("");
foreach (ABPerson itemPerson in _people) {
tmpCheckbox = new CheckboxElement (itemPerson.LastName);
cbPersonElements.Add(tmpCheckbox);
}
And then I add the list when I create the RootElement:
RootElement _rootElement = new RootElement ("People List"){
new Section ("People"){
cbPersonElements
}
How should I add a handler that will allow me to detected which CheckBoxElement was clicked.
I can't attach one to tmpCheckbox, that value changes with each iteration through the loop.
Seems like it should be simple, but I can't see it.
Thanks.
you should be able to use a ValueChanged handler
foreach (ABPerson itemPerson in _people) {
tmpCheckbox = new CheckboxElement (itemPerson.LastName);
tmpCheckbox.ValueChanged += delegate {
// do something here based on tmpCheckbox.Value
};
cbPersonElements.Add(tmpCheckbox);
}
I have the following code:
Section _section = new Section ("Test");
foreach (ExampleData data in Example.data) {
MessageElement Item = new MessageElement (){
Sender = data.Name,
Subject = data.Value,
Body = data.Description,
Date = data.Modified
} ;
_section.Add(Item);
var root = new RootElement("Item Expanded"){
new Section ("test2"){
new StringElement("Field Name", data.FieldName),
new StringElement("Value", data.Value),
new StringElement("Description", data.Description)
}
} ;
_section.Add(root);
} ;
var _rootElement = new RootElement ("Items") {
_section
} ;
I would like this to work in such a way that when a Message Element is tapped it shows the section with ("test2") that has the same data (e.g. the data was added during the same run of the loop.) I realize this will not happen currently, as it seems the Message Element
requires an Action delegate to do anything on a tap event, plus I'm adding everything to the same section. However, is there any way to replicate the behavior of multiple nested root elements and sections with a Message Element? If I create new pages/screens and try to transition that way, it rests the navigation controller and I lose the use of the back button, even if "push" is set to true.
Not sure what you want exactly. Replace your "Item Expanded" root element code with this to push a dialog viewcontoller on the navigation stack with a backbutton. Ofcourse your DialogViewcontroller should be in a UINavigation controller in the first place for this to work
Item.Tapped += delegate(DialogViewController arg1, UITableView arg2, NSIndexPath arg3)
{
var newDialogVC = new DialogViewController(
UITableViewStyle.Grouped,
new RootElement("Item Expanded")
{
new Section ("test2"){
new StringElement("Field Name", "test"),
new StringElement("Value", "test"),
new StringElement("Description", "test")
}
}
, true);
arg1.NavigationController.PushViewController(newDialogVC,true);
};
I have the following in a Section:
_favElement = new StyledStringElement (string.Empty);
_favElement.Alignment = UITextAlignment.Center;
if (_room.IsFavourite) {
_favElement.Image = UIImage.FromBundle ("Images/thumbs_up.png");
_favElement.Caption = "Unmark as Favourite";
} else {
_favElement.Image = null;
_favElement.Caption = "Mark as Favourite";
}
_favElement.Tapped += favElement_Tapped;
Then when I press the element I want the following to happen:
private void favElement_Tapped ()
{
if (_room.IsFavourite) {
_favElement.Image = null;
_favElement.Caption = "Mark as Favourite";
} else {
_favElement.Image = UIImage.FromBundle ("Images/thumbs_up.png");
_favElement.Caption = "Unmark as Favourite";
}
_room.IsFavourite = !_room.IsFavourite;
}
However the image and text does not change in the actual element when the element is tapped. Is there a refresh method or something that must be called? I've also tried changing the Accessory on Tapped as well and nothing changes. The properties behind do reflect the correct values though.
An alternative to reloading the UITableView is to reload the Element using code like this (copied from Touch.Unit):
if (GetContainerTableView () != null) {
var root = GetImmediateRootElement ();
root.Reload (this, UITableViewRowAnimation.Fade);
}
assuming that your code is in DialogViewController,add this
this.ReloadData();
but in your case I recommend you to use BooleanImageElement