mac 에서 NSButton(NSView)의 MouseEvent 받는 방법




목차


mono mac 을 쓰다보면, 정말이지 정신이 멍해지고, 손발에 마비가 오며, 호흡이 가까집니다. :)

.NET 도 알고, Objective-C 도 알고 있지만, mono 로 mac 개발을 하면 제3의 프레임웤을 만지는 기분이 들지요. 어쩌면 당연할지도 모르겠습니다. mono 가 개입하면서 Objective-C 도 아니고, .NET 도 아닌게 되어버렸으니까요.


흔히들 말하는 멘붕 상태가 되기 쉽상인데요, 이번 포스팅에서는 MouseEvent 를 받는 방법에 대해서 살펴볼까 합니다. .NET 을 잘 아시는 분이라면, 고민할 여지도 없는 것이겠지만, mono 라면 상황이 좀 달라집니다 -_-;




개요


일반적으로 NSControl 를 상속받는 NSButton 같은 모든 클래스에는 MouseDown 이라는 메소드가 존재합니다. 아래처럼 생겼습니다.



		public virtual void MouseDown (NSEvent theEvent)
		{
			NSApplication.EnsureUIThread ();
			if (theEvent == null)
			{
				throw new ArgumentNullException ("theEvent");
			}
			if (this.IsDirectBinding)
			{
				Messaging.void_objc_msgSend_IntPtr (base.Handle, NSControl.selMouseDown_Handle, theEvent.Handle);
			}
			else
			{
				Messaging.void_objc_msgSendSuper_IntPtr (base.SuperHandle, NSControl.selMouseDown_Handle, theEvent.Handle);
			}
		}


그런데 말이죠, MouseEntered, MouseExited 같은 메소드들도 존재합니다. 하지만, 전자와 후자의 메소드들은 조금 다릅니다. MouseDown 은 NSControl 에 정의되어 있는 반면, MouseEntered, MouseExited 같은 메소드들은 NSResponder 에 정의되어 있습니다. 이 NSResponder 가 어디쯤에 있는 클래스인지 궁금하실것 같아 설명드리자면. 아래처럼 되어 있습니다.



NSButton -> NSControl -> NSView -> NSResponder -> NSObject


구조를 보면 대략적으로 감이 오실것입니다. MouseDown 은 컨트롤에서 제공합니다. 버튼이 눌러졌다. 혹은 NSTextField 가 눌러졌다. 이런것을 인지하죠. MouseEntered, MouseExited 는 Control 이 아닌 녀석도 받을 수 있다는 말이 되겠습니다.




테스트 코드


'백문이 불여일견'이라고 테스트를 해 봅시다. NSButton 을 상속받은 EventButton 클래스를 만들었습니다.




public class EventButton : MonoMac.AppKit.NSButton
{
	public override void MouseDown (NSEvent theEvent)
	{
		Console.WriteLine ("Mouse Down!");
		base.MouseDown (theEvent);
	}

	public override void MouseEntered (NSEvent theEvent)
	{
		Console.WriteLine ("Mouse Entered!");
		base.MouseEntered (theEvent);
	}
}



이 버튼을 클릭하면, 의도한대로 "Mouse Down!" 이라는 문자가 출력됩니다. 마우스를 올리면 "Mouse Entered!" 가 정상적으로 출력 될까요? 안타깝게도 해당 메소드에 들어오지 않습니다. 충격적인가요?


mac 에서 이벤트가 동작하는 구조는 다음과 같습니다.


모든 NSResponder 를 상속받는 클래스에는 MouseEntered 메소드가 있습니다. 이 메소드가 호출되는 방식은 NSTrackingArea 라고 이벤트들을 트래킹 할 영역 정보를 가지고 있는 녀석을 사용합니다. 마우스가 움직일 때, 윈도우는 현재 마우스 위치를 트래킹 영역으로 지정한 녀석이 있는지 확인합니다. 그리고 그 영역이 등록된 녀석이 있으면, 그때서 MouseEntered 같은 메소드를 호출해 줍니다.


다시 말해서, 위 EventButton 은 윈도우가 이벤트를 호출해 줄 때, 받을 핸들러일 뿐입니다. 하지만 트래킹 영역이 등록되어 있지 않기 때문에, 절대 호출될리가 없지요. 그럼 무엇을 해야 할까요? 맞습니다. 트래킹 영역을 등록해 주어야 합니다.



public class EventButton : MonoMac.AppKit.NSButton
{
	public EventButton (IntPtr handle) : base (handle)
	{
		Initialize ();
	}

	// Called when created directly from a XIB file
	[Export ("initWithCoder:")]
	public EventButton (NSCoder coder) : base (coder)
	{
		Initialize ();
	}

	// Shared initialization code
	void Initialize ()
	{
		NSTrackingAreaOptions options = NSTrackingAreaOptions.MouseEnteredAndExited | NSTrackingAreaOptions.ActiveInActiveApp;
		NSTrackingArea area = new NSTrackingArea (this.Bounds, options, this, null);
		this.AddTrackingArea(area);
	}


	public override void MouseDown (NSEvent theEvent)
	{
		Console.WriteLine ("Mouse Down!");
		base.MouseDown (theEvent);
	}

	public override void MouseEntered (NSEvent theEvent)
	{
		Console.WriteLine ("Mouse Entered!");
		base.MouseEntered (theEvent);
	}
}



영역을 등록시키는 방법은 위 코드를 보시면 되겠습니다. NSTrackingArea 의 생성자를 간단히 살펴보죠.


첫번째는 트래킹할 영역입니다. .Net 이나 java 와는 다르게 컨트롤 자신이 마우스를 감지하는게 아니라, 그냥 영역 등록만 하고 윈도우가 알아서 해 줍니다. 무슨 이야긴가하면, 이 영역이 자신의 컨터롤보다 크거나, 혹은 전혀 다른 위치여도 상관이 없다는 이야깁니다. 버튼이 bounds 가 0,0,50,50 이더라도, 트래킹을 100,100,500,500 으로 해도 상관 없다는 이야깁니다. 이렇게 한다면, 화면상으로 버튼위에 마우스를 올렸을 때는 아무 반응이 없고, 100,100,500,500 위치에 마우스가 있을 때, 이벤트가 발생하게 됩니다.


두번째 파라미터는 어떤 이벤트들을 트래킹 할 것인지에 대한 파라미터 입니다.


세번째 파라미터는 이벤트를 받을 객체입니다.





글을 쓰고나니,, 왜 기본값으로 트래킹 영역 등록이 안되어 있는지 의문이네요 =_=;

윈도우 사용자라면 많이 혼라스러울 것 같습니다.