How to trigger an action from a NSTableCellView in view based NSTableView when using bindings - nstableview

I'm facing a problem with a view-based NSTableView running on 10.8 (target is 10.7, but I think this is not relevant).
I'm using an NSTableView, and I get content values for my custom NSTableCellView through bindings. I use the obejctValue of the NSTableCellView to get my data.
I added a button to my cell, and I'd like it to trigger some action when clicked. So far I have only been able to trigger an action within the custom NSTableCellView's subclass.
I can get the row that was clicked like this, using the chain:
NSButton *myButton = (NSButton*)sender;
NSTableView *myView = (NSTableView*)myButton.superview.superview.superview;
NSInteger rowClicked = [myView rowForView:myButton.superview];
From there I don't know how to reach my App Delegate or controller where the action is defined.
As I am using cocoa bindings, I do not have a delegate on the NSTableView that I could use to trigger my action.
Do you have any idea how I could talked back to controller ?
Many thanks in advance!

Although you are using bindings you can still set your controller as the delegate for your tableview in the interface builder.
I see that you already are able to access the table view from inside your cell. The next task must be simple, just set the table view delegate as the target for your button's action.

Thanks for your question, I also will be triggering an action from a button on a NSTableView. Your question helped to put me on the correct path.
First to address the your solution to finding which row number my NSTableView is on. I was able to find it without knowing the button, in my custom NSTableView I installed the following as a first attempt:
- (NSInteger)myRowNumber
{
return [(NSTableView*)self.superview.superview rowForView:self];
}
this works fine, however it is less than robust. It only works if you already know specifically how deep you are in the view hierarchy. A more robust and universal solution is:
- (NSInteger)myRowNumber
{
NSTableView* tableView = nil;
NSView* mySuperview = self;
do
{
NSView* nextSuper = mySuperview.superview;
if (nextSuper == nil)
{
NSException *exception =
[NSException exceptionWithName:#"NSTableView not found."
reason:[NSString stringWithFormat:#"%# search went too deep.",
NSStringFromSelector(_cmd)] userInfo:nil];
#throw exception;
}
if ([nextSuper isKindOfClass:[NSTableView class]])
tableView = (NSTableView*)nextSuper;
else
mySuperview = mySuperview.superview;
} while (tableView == nil);
return [tableView rowForView:self];
}
this not only works at the NSTableView level, but works with anything installed at any level above it, no matter how complex the view hierarchy.
As to the unanswered part of your question, I established an IBOutlet in my class and using interface builder tied if to my files owner (in my case my document class). Once I had a reference to the class I was sending my message to, and the row number, I call the function. In my case the call required that I pass the row number it originates from.
[self.myDoc doSomethingToRow:self.myRowNumber];
I tested this and it works at various levels of the view hierarchy above NSTableView. And it functions without having to have the row selected first (which appears to be assumed in Apples documentation).
Regards, George Lawrence Storm, Maltby, Washington, USA

Use rowForView: and the responder chain
To respond to a control's action embedded within an NSTableCellView, the control should issue the action to the First Responder. Alternatively, File Owner is possible but this is more tightly coupled.
Use rowForView: within the action method to determine which row's control issued the action:
- (IBAction)revealInFinder:(id)sender {
NSInteger row = [self.tableView rowForView:sender];
...
}
The action is implemented within any of the responder chain classes. Most likely, this will be your subclassed NSWindowController instance. The responder could also be the application delegate; assuming the delegate has a means to talk to the NSTableView.
See Apple's example TableViewPlayground: Using View-Based NSTableView and NSOutlineView to see this in action.

Suhas answer helped me.
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
if let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "EDIT_CELL_VIEW"), owner: self) as? SymbolManagerCell {
if let editButton = cell.subviews[0] as? NSButton {
editButton.target = cell // this is required to trigger action
}
return cell
}
return nil
}

Related

Wrong record displayed when create form closes

Inside AX2012 R3, when creating a new Return Order from the Return Order list view page (using the button in the Header), the SalesCreateOrder form opens and functions as expected.
Upon close of this form, however, instead of opening the newly created Order, instead the order that was selected in the grid is opening.
Several developers have made customization to this form, but none that (at first glance) appear relevant to this behavior.
Where would I find the behavior to open a form upon close of the SalesCreateOrder dialog?
You can open the created order by changing the SalesCreateOrder.close method:
public void close()
{
Args args = new Args(this); //Change here
// Save user's customer search type
MCRCustSearch::saveCustSearchType(mcrCustSearchType.selection());
if (salesTableType)
{
salesTableType.formMethodClose();
}
//Change here -->
args.record(salesTable);
new MenuFunction(menuitemDisplayStr(SalesTable),MenuItemType::Display).run(args);
//End of change <--
super();
}
You may have to change the called menuitem if called from Return order.
Your understanding of how returns are created is wrong. A form isn't opened upon closing, it's opened upon creation.
When you do Ctrl+n or click to create a new return order, the ReturnTable form actually instantiates the SalesCreateOrder form eventually.
To see this, place a breakpoint in the init method of the ReturnTable at \Forms\ReturnTable\Methods\init and then try and create a new return order.

Change width of a lookup column

I've created a lookup with two columns, first one containing and integer which works just fine but the second one has a long name and this is where the problem arises. Users should horizontally scroll in order to check the entire string and even in that case, the column's width is not big enough to display the whole data.
I've found this :
Adjusting column width on form control lookup
But i don't understand exactly where and what to add.
I am not sure but maybe I have to add the fact that this lookup is used on a menu item which points to an SSRS report, in the parameters section.
Update 1:
I got it working with a lookup form called like this :
Args args;
FormRun formRun;
;
args = new Args();
args.name(formstr(LookupOMOperatingUnit));
args.caller(_control);
formRun = classfactory.formRunClass(args);
formRun.init();
_control.performFormLookup(formRun);
and in the init method of this form i added:
public void init()
{
super();
element.selectMode(OMOperatingUnit_OMOperatingUnitNumber);
}
meaning the field i really need.
I am not sure i understand the mechanism completely but it seems it knows how to return this exact field to the DialogField from where it really started.
In order to make it look like a lookup, i have kept the style of the Design as Auto but changed the WindowType to Popup, HideToolBar to Yes and Frame to Border.
Probably the best route is do a custom lookup and change the extended data type of the key field to reflect that. In this way the change is reflected in all places. See form FiscalCalendarYearLookup and EDT FiscalYearName as an example of that.
If you only need to change a single place, the easy option is to override performFormLookup on the calling form. You should also override the DisplayLength property of the extended data type of the long field.
public void performFormLookup(FormRun _form, FormStringControl _formControl)
{
FormGridControl grid = _form.control(_form.controlId('grid'));
grid.autoSizeColumns(false);
super(_form,_formControl);
}
This will not help you unless you have a form, which may not be the case in this report scenario.
Starting in AX 2009 the kernel by default auto-updates the control sizes based on actual record content. This was a cause of much frustration as the sizes was small when there was no records and these sizes were saved! Also the performance of the auto-update was initially bad in some situations. As an afterthought the grid control autoSizeColumns method was provided but it was unfortunately never exposed as a property.
you can extends the sysTableLookup class and override the buildFromGridDesign method to set the grid control width.
protected void buildFormGridDesign(FormBuildGridControl _formBuildGridControl)
{
if (gridWidth > 0)
{
_formBuildGridControl.allowEdit(true);
_formBuildGridControl.showRowLabels(false);
_formBuildGridControl.widthMode(2);
_formBuildGridControl.width(gridWidth);
}
else
{
super(_formBuildGridControl);
}
}

Orchard CMS - determining if Model is valid in Content Item Driver

In my instance of Orchard, I have a custom content type which has a custom content part. In the "editor driver" for the content part, I need to check to see if the container content item is valid (i.e. passes validation).
The normal ModelState won't work here because of how Orchard works - and I can determine if the content part is valid, but I need to know about the entire content item (there are other content parts within the content item).
I know there are ways to execute code once a content part is published / created using lifecycle events (http://docs.orchardproject.net/Documentation/Understanding-content-handlers), but there's no way (that I know of) to pass those events information.
Basically, I need to execute a method if the content item is valid, and I need to pass the method information contained within the ViewModel.
There may be (and probably is) a better way to do this, but I'm struggling to find a way within Orchards framework.
sample code:
//POST
protected override DriverResult Editor(EventPart part, IUpdateModel updater, dynamic shapeHelper)
{
var viewModal = new EventEditViewModel();
if (updater.TryUpdateModel(viewModal, Prefix, null, null))
{
part.Setting = viewModal.Setting;
}
//here's where I need to check if the CONTENT ITEM is valid or not, for example
if (*valid*)
{
DoSomething(viewModal.OtherSetting);
}
return Editor(part, shapeHelper);
}
Note: I am using Orchard version 1.6.
No easy way to do that from inside a driver, I'm afraid. Too early. You can access other parts by doing part.As<OtherPart>, but those may or may not be updated yet at this point.
You may try utilizing handlers and OnPublishing/OnPublished (and other) events like this:
OnPublishing<MyPart>((ctx, part) =>
{
// Do some validation checks on other parts
if (part.As<SomeOtherPart>().SomeSetting == true)
{
notifier.Error(T("SomeSetting cannot be true."));
transactions.Cancel();
}
});
where transactions is ITransactionManager instance, injected in ctor.
If you need more control, writing your own controller for handling item updates/creates is the best way to go.
In order to do that (assuming you already have your controller in place), you need to use handler OnGetContentItemMetadata method to point Orchard to use your controller instead of the default one, like this:
OnGetContentItemMetadata<MyPart>((context, part) =>
{
// Edit item action
context.Metadata.EditorRouteValues = new RouteValueDictionary {
{"Area", "My.Module"},
{"Controller", "Item"},
{"Action", "Edit"},
{"id", context.ContentItem.Id}};
// Create new item action
context.Metadata.CreateRouteValues = new RouteValueDictionary {
{"Area", "My.Module"},
{"Controller", "Item"},
{"Action", "Create"});
});

Vaadin databinding between ListSelect and java.util.List<String>

I am new to vaadin and have a databinding problem. I have posted allready in the vaadin forum, but no answer up to now.
if you answer here, I will of course reward it anyway.
https://vaadin.com/forum/-/message_boards/view_message/1057226
thanks in advance.
greets,
Andreas
Additional information: I tried allready to iterate over the items in the container, after pressing a save button. After deleting all original elements in the model collection, and adding copies from the container, the GUI breaks. Some other GUI elements do not respond anymore.
I have personally never used ListSelect, but I found this from the API docs:
This is a simple list select without, for instance, support for new items, lazyloading, and other advanced features.
I'd recommend BeanItemContainer. You can use it like this:
// Create a list of Strings
List<String> strings = new ArrayList<String>();
strings.add("Hello");
// Create a BeanItemContainer and include strings list
final BeanItemContainer<String> container = new BeanItemContainer<String>(strings);
container.addBean("World");
// Create a ListSelect and make BeanItemContainer its data container
ListSelect select = new ListSelect("", container);
// Create a button that adds "!" to the list
Button button = new Button("Add to list", new Button.ClickListener() {
public void buttonClick(ClickEvent event) {
container.addBean("!");
}
}
// Add the components to a layout
myLayout.addComponent(button);
myLayout.addComponent(select);
The downside (or benefit, it depends :) of this is that you can't add duplicate entries to a BeanItemContainer. In the example above the exclamation mark gets only added once.
You can get a Collection of Strings by calling:
Collection<String> strings = container.getItemIds();
If you need to support duplicate entries, take a look at IndexedContainer. With IndexedContainer you can add a String property by calling myIndexedContainer.addContainerProperty("caption", String.class, ""); and give each Item a unique itemId (or let the container generate the id's automatically).
Im not sure I understand your problem but I belive that it might be that you haven't told the controller to repaint. You do this be setting the datasource like this after the save event has occured.
listSelect.setContainerDataSource(listSelect.getContainerDataSource());

ActionScript/Flex: Augment MouseEvents with extra information

I've got a business class, Spam and the corresponding view class, SpamView.
How can I augment MouseEvents coming out of SpamView so the MouseEvents which come out of it contain a reference to the instance of Spam which the SpamView is displaying?
Here's how I'd like to use it:
class ViewContainer {
...
for each (spam in spams) {
addChild(new SpamView(spam));
...
function handleMouseMove(event:MouseEvent) {
if (event is SpamViewMouseEvent)
trace("The mouse is being moved over spam:", spam)
}
}
Thanks!
Things I've considered which don't work:
Adding event listeners to each SpamView: the book keeping (making sure that they are added/removed properly) is a pain.
Using event.target: the event's target may be a child of the SpamView (which isn't very useful)
Listening for a MouseEvent, creating a new SpamViewMouseEvent, copying all the fields over, then dispatching that: copying all the fields manually is also a pain.
There are multiple ways to solve this puppy. I would use your #2 option, but build a utility function that gets all of the spamViews on the screen and do a couple of if-elses in looping over your spamViews.
var targ : DisplayObject = DisplayObject(event.target)
If(targ is SpamView) //then you know what's up.
If( loopedSpamView.contains(targ) ) // then the target is inside the spamViewContainer and you should be cool.
Best of luck,
Jeremy

Resources