2
votes

I want to both read and write the scroll position (horizontal and vertical) inside an NSScrollView using MonoMac. I do this because I want to save and load several different states which include the scroll positions. Instead of having several different NSScrollViews I just have a single one and want to change it whenever the state changes.

I have so far figured out that I am interested in the DoubleValue of the NSScrollView's HorizontalScroller and VerticalScroller. But I cannot for my life figure out how to detect when this value changes so I can save it. I need to detect the change no matter if the user is clicking the scroll bar, dragging it, or using either a mouse or a trackpad. As long as the scrollbar moves, I want to save the position.

Any suggestions?

2

2 Answers

3
votes

I think you are approaching this the wrong way. My understanding is, it is not best practice to interact with the NSScroller directly (nor, do I think, this will work).

See the answer to this question, which I think is similar to your scenario. The best thing to do is to set the origin of the scroll view's ContentView.

I converted the answer of that question to C#:

public override void AwakeFromNib()
{
    tableView.EnclosingScrollView.ContentView.PostsBoundsChangedNotifications = true;
    NSNotificationCenter.DefaultCenter.AddObserver(this, new Selector("boundsDidChangeNotification"), 
    NSView.BoundsChangedNotification, tableView.EnclosingScrollView.ContentView);

    base.AwakeFromNib();
}

[Export("boundsDidChangeNotification")]
public void BoundsDidChangeNotification(NSObject o)
{
    var notification = o as NSNotification;
    var view = notification.Object as NSView;
    var position = view.Bounds.Location;
    Console.WriteLine("Scroll position: " + position.ToString());
} 

You can scroll to a specific point something like this:

PointF scrollTo = new PointF(19, 1571);
tableView.EnclosingScrollView.ContentView.ScrollToPoint(scrollTo);
tableView.EnclosingScrollView.ReflectScrolledClipView(tableView.EnclosingScrollView.ContentView);

You may find this interesting: Scroll View Programming Guide for Mac

3
votes

Since Xamarin.Mac 1.12 the solution posted by @TheNextman needs some adjustments. BoundsDidChangeNotification must have no parameter otherwise a System.AggregateException will be thrown in Main. If you don't need that parameter in the selector code, just delete it from method signature and it will work. But if you need then it you must use some other version of the AddObserver method

public override void AwakeFromNib()
{
    tableView.EnclosingScrollView.ContentView.PostsBoundsChangedNotifications = true;
    NSNotificationCenter.DefaultCenter.AddObserver(NSView.BoundsChangedNotification, BoundsDidChangeNotification, tableView.EnclosingScrollView.ContentView);

    base.AwakeFromNib();
}

public void BoundsDidChangeNotification(NSNotification notification)
{
    var view = notification.Object as NSView;
    var position = view.Bounds.Location;
    Console.WriteLine("Scroll position: " + position.ToString());
} 

Note that the action method can also be implemented inline using a lambda structure

UPDATE After talking with the support it seems that some component has become more picky. You can still use the selector based solution with a paramater but you must add a semicolon at the end of the selector name

[Export("boundsDidChangeNotification:")]
public void BoundsDidChangeNotification(NSObject o)