如何在最顶层的 NSPanel 中获取键盘事件?
How do I get keyboard events in the topmost NSPanel?
我使用 Xamarin 创建了一个应用程序来帮助在线观看电影。它在所有其他 windows 之上显示字幕。这是使用 NSPanel
完成的,因为这是使其在 MacOS Mojave 上运行的唯一方法。
该应用运行良好。现在我想通过使 NSPanel
响应键盘事件来改进应用程序,这样我就可以通过使用键盘来控制应用程序暂停、播放、后退或前进。
如何在最顶层获得键盘事件NSPanel
?
我尝试使用此代码:
NSEvent.AddLocalMonitorForEventsMatchingMask(NSEventMask.KeyDown, KeyboardEventHandler);
private static NSEvent KeyboardEventHandler(NSEvent keyEvent)
{
// handle key down events here
return (keyEvent);
}
但它只在应用程序未处于全屏模式时有效。
可以找到完整的 SubtitlesViewer-MACOS
项目 here。
这是创建面板的代码部分:
public override void ViewWillAppear()
{
base.ViewWillAppear();
SetupView();
}
private void SetupView()
{
var screenRes = screenResolution();
int PANEL_HEIGHT = 200;
subtitlesPanel = new NSPanel
(
new CoreGraphics.CGRect(40, 50, screenRes.Width - 80, PANEL_HEIGHT),
NSWindowStyle.Titled | NSWindowStyle.Closable | NSWindowStyle.Resizable | NSWindowStyle.Miniaturizable | NSWindowStyle.DocModal,
NSBackingStore.Buffered, true
)
{
BackgroundColor = NSColor.FromCalibratedRgba(0, 0, 0, 0.0f),
ReleasedWhenClosed = true,
HidesOnDeactivate = false,
FloatingPanel = true,
StyleMask = NSWindowStyle.NonactivatingPanel,
Level = NSWindowLevel.MainMenu - 1,
IsMovable = true,
CollectionBehavior = NSWindowCollectionBehavior.CanJoinAllSpaces |
NSWindowCollectionBehavior.FullScreenAuxiliary
};
subtitlesPanel.OrderFront(null);
subtitleTextButton = new NSButton(new CoreGraphics.CGRect(40, 0, screenRes.Width - 120, PANEL_HEIGHT-30))
{
Title = "",
WantsLayer = true
};
subtitleTextButton.Layer.BackgroundColor = NSColor.Clear.CGColor;
subtitleTextField = new NSTextField(new CoreGraphics.CGRect(40, 0, screenRes.Width - 120, PANEL_HEIGHT-30))
{
Alignment = NSTextAlignment.Center
};
subtitleTextField.Cell.Alignment = NSTextAlignment.Center;
forwardButton = new NSButton(new CoreGraphics.CGRect(0, 0, 40, 30));
forwardButton.Title = ">>";
forwardButton.Activated += (object sender, EventArgs e) => {
subtitlesProvider.Forward();
};
backButton = new NSButton(new CoreGraphics.CGRect(0, 30, 40, 30));
backButton.Title = "<<";
backButton.Activated += (object sender, EventArgs e) => {
subtitlesProvider.Back();
};
startStopButton = new NSButton(new CoreGraphics.CGRect(0, 60, 40, 30));
startStopButton.Title = "Play";
startStopButton.Activated += (object sender, EventArgs e) => {
subtitlesProvider.StartStop(subtitlesProvider.Playing);
};
subtitlesPanel.ContentView.AddSubview(subtitleTextButton, NSWindowOrderingMode.Below, null);
subtitlesPanel.ContentView.AddSubview(subtitleTextField, NSWindowOrderingMode.Below, null);
subtitlesPanel.ContentView.AddSubview(forwardButton, NSWindowOrderingMode.Below, null);
subtitlesPanel.ContentView.AddSubview(backButton, NSWindowOrderingMode.Below, null);
subtitlesPanel.ContentView.AddSubview(startStopButton, NSWindowOrderingMode.Below, null);
SetupSubtitlesProvider();
}
请指教我还应该尝试什么才能让它发挥作用。
我在这里找到了解决方案:
keyDown not being called
这是我实现的 NSPanelExt class 来处理键。
public class NSPanelExt : NSPanel
{
public KeyPressedHandler KeyPressed;
public delegate void KeyPressedHandler(KeyCodeEventArgs e);
public NSPanelExt(CGRect contentRect, NSWindowStyle aStyle, NSBackingStore bufferingType, bool deferCreation) : base(contentRect, aStyle, bufferingType, deferCreation)
{
}
public override bool CanBecomeMainWindow => true;
public override bool CanBecomeKeyWindow => true;
public override bool AcceptsFirstResponder()
{
return true;
}
public override void KeyDown(NSEvent theEvent)
{
// this function is never called
KeyPressed?.Invoke(new KeyCodeEventArgs { Key = GetKeyCode(theEvent.KeyCode) });
}
private KeyCode GetKeyCode(ushort keyCode)
{
KeyCode result = KeyCode.Unknown;
switch (keyCode)
{
case 123:
result = KeyCode.Left;
break;
case 49:
result = KeyCode.Space;
break;
case 124:
result = KeyCode.Right;
break;
case 53:
result = KeyCode.Esc;
break;
}
return result;
}
我还更新了 ViewController 以保持 NSPanel 始终处于活动状态。
public partial class ViewController : NSViewController
{
// ...
private NSButton startStopButton;
Timer _timer = new Timer();
private void SetupView()
{
// ...
subtitlesPanel.KeyPressed += SubtitlesPanel_KeyPressed;
// ...
IntializeKeepWindowFocusedTimer();
}
void SubtitlesPanel_KeyPressed(KeyCodeEventArgs e)
{
switch(e.Key)
{
case KeyCode.Left:
backButton.PerformClick(this);
break;
case KeyCode.Right:
forwardButton.PerformClick(this);
break;
case KeyCode.Space:
startStopButton.PerformClick(this);
break;
case KeyCode.Esc:
_timer.Stop();
break;
}
}
private void IntializeKeepWindowFocusedTimer()
{
_timer.Interval = 200; //in milliseconds
_timer.Elapsed += Timer_Elapsed;;
_timer.AutoReset = true;
_timer.Enabled = true;
}
void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
NSApplication.SharedApplication.BeginInvokeOnMainThread(() =>
{
subtitlesPanel.MakeKeyWindow();
if (SetSubtitleNeeded)
{
subtitlesProvider.SetSubTitle(0);
startStopButton.Title = "Stop";
SetSubtitleNeeded = false;
_timer.Interval = 5000;
}
});
}
private bool SetSubtitleNeeded = false;
partial void ClickedButton(NSObject sender)
{
_timer.Stop();
var nsUrl = subtitleFileSelector.GetFile();
if (nsUrl == null)
return;
fileName = nsUrl.Path;
subtitlesProvider.ReadFromFile(fileName);
SetSubtitleNeeded = true;
_timer.Start();
}
我使用 Xamarin 创建了一个应用程序来帮助在线观看电影。它在所有其他 windows 之上显示字幕。这是使用 NSPanel
完成的,因为这是使其在 MacOS Mojave 上运行的唯一方法。
该应用运行良好。现在我想通过使 NSPanel
响应键盘事件来改进应用程序,这样我就可以通过使用键盘来控制应用程序暂停、播放、后退或前进。
如何在最顶层获得键盘事件NSPanel
?
我尝试使用此代码:
NSEvent.AddLocalMonitorForEventsMatchingMask(NSEventMask.KeyDown, KeyboardEventHandler);
private static NSEvent KeyboardEventHandler(NSEvent keyEvent)
{
// handle key down events here
return (keyEvent);
}
但它只在应用程序未处于全屏模式时有效。
可以找到完整的 SubtitlesViewer-MACOS
项目 here。
这是创建面板的代码部分:
public override void ViewWillAppear()
{
base.ViewWillAppear();
SetupView();
}
private void SetupView()
{
var screenRes = screenResolution();
int PANEL_HEIGHT = 200;
subtitlesPanel = new NSPanel
(
new CoreGraphics.CGRect(40, 50, screenRes.Width - 80, PANEL_HEIGHT),
NSWindowStyle.Titled | NSWindowStyle.Closable | NSWindowStyle.Resizable | NSWindowStyle.Miniaturizable | NSWindowStyle.DocModal,
NSBackingStore.Buffered, true
)
{
BackgroundColor = NSColor.FromCalibratedRgba(0, 0, 0, 0.0f),
ReleasedWhenClosed = true,
HidesOnDeactivate = false,
FloatingPanel = true,
StyleMask = NSWindowStyle.NonactivatingPanel,
Level = NSWindowLevel.MainMenu - 1,
IsMovable = true,
CollectionBehavior = NSWindowCollectionBehavior.CanJoinAllSpaces |
NSWindowCollectionBehavior.FullScreenAuxiliary
};
subtitlesPanel.OrderFront(null);
subtitleTextButton = new NSButton(new CoreGraphics.CGRect(40, 0, screenRes.Width - 120, PANEL_HEIGHT-30))
{
Title = "",
WantsLayer = true
};
subtitleTextButton.Layer.BackgroundColor = NSColor.Clear.CGColor;
subtitleTextField = new NSTextField(new CoreGraphics.CGRect(40, 0, screenRes.Width - 120, PANEL_HEIGHT-30))
{
Alignment = NSTextAlignment.Center
};
subtitleTextField.Cell.Alignment = NSTextAlignment.Center;
forwardButton = new NSButton(new CoreGraphics.CGRect(0, 0, 40, 30));
forwardButton.Title = ">>";
forwardButton.Activated += (object sender, EventArgs e) => {
subtitlesProvider.Forward();
};
backButton = new NSButton(new CoreGraphics.CGRect(0, 30, 40, 30));
backButton.Title = "<<";
backButton.Activated += (object sender, EventArgs e) => {
subtitlesProvider.Back();
};
startStopButton = new NSButton(new CoreGraphics.CGRect(0, 60, 40, 30));
startStopButton.Title = "Play";
startStopButton.Activated += (object sender, EventArgs e) => {
subtitlesProvider.StartStop(subtitlesProvider.Playing);
};
subtitlesPanel.ContentView.AddSubview(subtitleTextButton, NSWindowOrderingMode.Below, null);
subtitlesPanel.ContentView.AddSubview(subtitleTextField, NSWindowOrderingMode.Below, null);
subtitlesPanel.ContentView.AddSubview(forwardButton, NSWindowOrderingMode.Below, null);
subtitlesPanel.ContentView.AddSubview(backButton, NSWindowOrderingMode.Below, null);
subtitlesPanel.ContentView.AddSubview(startStopButton, NSWindowOrderingMode.Below, null);
SetupSubtitlesProvider();
}
请指教我还应该尝试什么才能让它发挥作用。
我在这里找到了解决方案: keyDown not being called
这是我实现的 NSPanelExt class 来处理键。
public class NSPanelExt : NSPanel
{
public KeyPressedHandler KeyPressed;
public delegate void KeyPressedHandler(KeyCodeEventArgs e);
public NSPanelExt(CGRect contentRect, NSWindowStyle aStyle, NSBackingStore bufferingType, bool deferCreation) : base(contentRect, aStyle, bufferingType, deferCreation)
{
}
public override bool CanBecomeMainWindow => true;
public override bool CanBecomeKeyWindow => true;
public override bool AcceptsFirstResponder()
{
return true;
}
public override void KeyDown(NSEvent theEvent)
{
// this function is never called
KeyPressed?.Invoke(new KeyCodeEventArgs { Key = GetKeyCode(theEvent.KeyCode) });
}
private KeyCode GetKeyCode(ushort keyCode)
{
KeyCode result = KeyCode.Unknown;
switch (keyCode)
{
case 123:
result = KeyCode.Left;
break;
case 49:
result = KeyCode.Space;
break;
case 124:
result = KeyCode.Right;
break;
case 53:
result = KeyCode.Esc;
break;
}
return result;
}
我还更新了 ViewController 以保持 NSPanel 始终处于活动状态。
public partial class ViewController : NSViewController
{
// ...
private NSButton startStopButton;
Timer _timer = new Timer();
private void SetupView()
{
// ...
subtitlesPanel.KeyPressed += SubtitlesPanel_KeyPressed;
// ...
IntializeKeepWindowFocusedTimer();
}
void SubtitlesPanel_KeyPressed(KeyCodeEventArgs e)
{
switch(e.Key)
{
case KeyCode.Left:
backButton.PerformClick(this);
break;
case KeyCode.Right:
forwardButton.PerformClick(this);
break;
case KeyCode.Space:
startStopButton.PerformClick(this);
break;
case KeyCode.Esc:
_timer.Stop();
break;
}
}
private void IntializeKeepWindowFocusedTimer()
{
_timer.Interval = 200; //in milliseconds
_timer.Elapsed += Timer_Elapsed;;
_timer.AutoReset = true;
_timer.Enabled = true;
}
void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
NSApplication.SharedApplication.BeginInvokeOnMainThread(() =>
{
subtitlesPanel.MakeKeyWindow();
if (SetSubtitleNeeded)
{
subtitlesProvider.SetSubTitle(0);
startStopButton.Title = "Stop";
SetSubtitleNeeded = false;
_timer.Interval = 5000;
}
});
}
private bool SetSubtitleNeeded = false;
partial void ClickedButton(NSObject sender)
{
_timer.Stop();
var nsUrl = subtitleFileSelector.GetFile();
if (nsUrl == null)
return;
fileName = nsUrl.Path;
subtitlesProvider.ReadFromFile(fileName);
SetSubtitleNeeded = true;
_timer.Start();
}