问题
I have created a sidebar inside my C#/MonoMac application using an NSOutlineView and got help creating the items in the question Create NSView programatically in Xamarin Studio. But now I have made some items in the outline view editable and of course I want to detect when the items are edited and take action (edit the underlying model, or create a new one if a special "Create new" item was edited).
The items can be edited alright, either by double click or pressing Return when it's selected. But I can't figure out how to detect when an item was edited in my code. I have tried to override CommitEditing in my NSOutlineViewDataSource, and override both CommitEditing and ShouldEditTableColumn in my NSOutlineViewDelegate. Neither of these seems to get called.
I have also tried to hook up an event handler to EditingEnded to the NSTextField which is a subview to the view I get when I call MakeView in my NSOutlineViewDelegate.GetView method. But this makes the application crash. I also get a crash when I try to set the Delegate of the NSTextField and in it override the EditingEnded or TextShouldEndEditing. The crash happens as soon as I press a key after entering edit mode.
Now I am lost. It feels as if I've tried everything.
In case you want to look at the code it's here:
The model: https://code.google.com/p/yet-another-music-application/source/browse/trunk/Player/GUIs/OSX/Models/Navigation.cs
The view controller: https://code.google.com/p/yet-another-music-application/source/browse/trunk/Player/GUIs/OSX/Views/NavigationViewController.cs
Here's a crash from when I subscribe to the Changed event using this code:
// ...
view = outlineView.MakeView ("DataCell", this);
((NSTextField)view.Subviews [1]).Changed += foo;
}
private void foo(object sender, EventArgs e) {
Console.WriteLine("changed text field.");
};
2013-08-02 08:53:32.851 Stoffi[6582:1007] -[__NSCFType controlTextDidEndEditing:]: unrecognized selector sent to instance 0x16a6c40 2013-08-02 08:53:32.852 Stoffi[6582:1007] Exception detected while handling key input. 2013-08-02 08:53:32.852 Stoffi[6582:1007] -[__NSCFType controlTextDidEndEditing:]: unrecognized selector sent to instance 0x16a6c40 2013-08-02 08:53:32.863 Stoffi[6582:1007] ( 0 CoreFoundation 0x9966be8b __raiseError + 219 1 libobjc.A.dylib 0x98a8f52e objc_exception_throw + 230 2 CoreFoundation 0x9966fafd -[NSObject(NSObject) doesNotRecognizeSelector:] + 253 3 CoreFoundation 0x995b7e87 ___forwarding___ + 487 4 CoreFoundation 0x995b7c32 _CF_forwarding_prep_0 + 50 5 Foundation 0x93d36e52 __57-[NSNotificationCenter addObserver:selector:name:object:]_block_invoke_0 + 49 6 CoreFoundation 0x9962d851 ___CFXNotificationPost_block_invoke_0 + 257 7 CoreFoundation 0x99578e8a _CFXNotificationPost + 2794 8 Foundation 0x93d1f988 -[NSNotificationCenter postNotificationName:object:userInfo:] + 92 9 AppKit 0x972b9926 -[NSTextField textDidEndEditing:] + 405 10 Foundation 0x93d36e52 __57-[NSNotificationCenter addObserver:selector:name:object:]_block_invoke_0 + 49 11 CoreFoundation 0x9962d851 ___CFXNotificationPost_block_invoke_0 + 257 12 CoreFoundation 0x99578e8a _CFXNotificationPost + 2794 13 Foundation 0x93d1f988 -[NSNotificationCenter postNotificationName:object:userInfo:] + 92 14 AppKit 0x96fd762b -[NSTextView(NSPrivate) _giveUpFirstResponder:] + 434 15 AppKit 0x96fd746f -[NSTextView(NSKeyBindingCommands) insertNewline:] + 580 16 libobjc.A.dylib 0x98a9c5d3 -[NSObject performSelector:withObject:] + 70 17 AppKit 0x96fd714e -[NSResponder doCommandBySelector:] + 91 18 AppKit 0x96fd6f83 -[NSTextView doCommandBySelector:] + 152 19 AppKit 0x97067fe3 -[NSTextInputContext doCommandBySelector:] + 121 20 AppKit 0x97067f5d -[NSTextInputContext _handleCommand:] + 84 21 AppKit 0x97062929 -[NSKeyBindingManager(NSKeyBindingManager_MultiClients) interpretEventAsCommand:forClient:] + 2006 22 AppKit 0x97061db5 -[NSTextInputContext handleEvent:] + 1298 23 AppKit 0x97061825 -[NSView interpretKeyEvents:] + 205 24 AppKit 0x96fa43b8 -[NSTextView keyDown:] + 680 25 AppKit 0x971c8af1 -[NSWindow sendEvent:] + 7432 26 AppKit 0x971c390f -[NSApplication sendEvent:] + 4278 27 AppKit 0x970dd62c -[NSApplication run] + 951 28 AppKit 0x970805f6 NSApplicationMain + 1053 29 ??? 0x060c9e76 0x0 + 101490294 30 ??? 0x060c9c70 0x0 + 101489776 31 ??? 0x007beff8 0x0 + 8122360 32 ??? 0x007bf156 0x0 + 8122710 33 libmono-2.0.dylib 0x0100be52 mono_jit_runtime_invoke + 722 34 libmono-2.0.dylib 0x011a767a mono_runtime_invoke + 170 35 libmono-2.0.dylib 0x011aa1f1 mono_runtime_exec_main + 705 36 libmono-2.0.dylib 0x011a9401 mono_runtime_run_main + 929 37 libmono-2.0.dylib 0x010695e5 mono_jit_exec + 149 38 libmono-2.0.dylib 0x0106bb79 mono_main + 9609 39 Stoffi 0x0000308f main + 2047 40 Stoffi 0x00002885 start + 53 )
回答1:
Subscribing the event on the text field inside GetView
works fine for me. The issue must exist somewhere else in your code...
Edit to add:
Be wary of doing stuff like this:
((NSTextField)view.Subviews [1]).Delegate = new PlaylistNavigationDelegate ();
And, assigning the event to an anonymous method as per your original attempt.
In this case, once control leaves the scope of GetView
, the only reference to your delegate is on the unmanaged side. The Mono GC can't see that, and the delegate becomes eligible for collection. This can cause all kind of issues when the managed reference is garbage collected out from underneath Cocoa.
Working sample:
// MainWindowController...
public override void AwakeFromNib()
{
rootNode = new Node(new NSString("Root Node"));
for(int i = 1; i <=5; i++)
{
rootNode.Children.Add(new Node(new NSString("Child Node")));
}
sourceList.Delegate = new SourceListDelegate(this);
sourceList.DataSource = new SourceListDataSource(this);
base.AwakeFromNib();
}
private class SourceListDataSource : NSOutlineViewDataSource
{
private MainWindowController controller;
public SourceListDataSource(MainWindowController controller)
{
this.controller = controller;
}
public override int GetChildrenCount(NSOutlineView outlineView, NSObject item)
{
if(item != null)
{
return (int)((Node)item).Count;
}
return (int)controller.rootNode.Count;
}
public override bool ItemExpandable(NSOutlineView outlineView, NSObject item)
{
if(item == null)
{
return false;
}
return !((Node)item).Leaf;
}
public override NSObject GetChild(NSOutlineView outlineView, int childIndex, NSObject item)
{
if(item == null)
{
return (Node)Runtime.GetNSObject(controller.rootNode.Children.ValueAt((uint)childIndex));
}
Node theItem = (Node)item;
return (Node)Runtime.GetNSObject(theItem.Children.ValueAt((uint)childIndex));
}
public override NSObject GetObjectValue(NSOutlineView outlineView, NSTableColumn tableColumn, NSObject item)
{
if(item != null)
{
return ((Node)item).Text;
}
return new NSString("");
}
}
private class SourceListDelegate : NSOutlineViewDelegate
{
private MainWindowController controller;
public SourceListDelegate(MainWindowController controller)
{
this.controller = controller;
}
public override bool IsGroupItem(NSOutlineView outlineView, NSObject item)
{
return (Node)item == controller.rootNode;
}
public override bool ShouldEditTableColumn(NSOutlineView outlineView, NSTableColumn tableColumn, NSObject item)
{
return true;
}
public override NSView GetView(NSOutlineView outlineView, NSTableColumn tableColumn, NSObject item)
{
if(IsGroupItem(outlineView, item))
{
return outlineView.MakeView("HeaderCell", this);
}
var view = outlineView.MakeView("DataCell", this);
var node = (Node)item;
((NSTextField)view.Subviews[1]).StringValue = node.Text;
((NSTextField)view.Subviews[1]).Changed += HandleChanged;
return view;
}
void HandleChanged (object sender, EventArgs e)
{
Console.WriteLine("changed");
}
}
private class Node : NSObject
{
private NSMutableArray children = new NSMutableArray();
public NSMutableArray Children
{
get
{
return children;
}
}
public uint Count
{
get
{
return Children.Count;
}
}
public bool Leaf
{
get
{
return this.Count == 0;
}
}
public NSString Text { get; set; }
public Node(NSString text)
{
this.Text = text;
}
}
来源:https://stackoverflow.com/questions/17889328/listen-to-events-from-programatically-created-nsview