确保第一个按钮是带有TabIndex=0
的"One and Only"控件,这样它才能获得焦点,但这和被Tab键插入之间应该没有区别.
如果你 Select "定制油漆",你可以通过手动将所有的Button
换成设计者.cs文件中的ButtonEx
来使它的侵害性降到最低.然后,只需很少的代码,就可以拥有外焦点提示实心边框和虚线焦点矩形的设计时属性.
class ButtonEx : Button
{
public Color FocusRectangleColor
{
get => _focusRectangleColor;
set
{
if (!Equals(_focusRectangleColor, value))
{
_focusRectangleColor = value;
Refresh();
}
}
}
Color _focusRectangleColor = Color.Red;
public Color FocusCueColor
{
get => _focusCueColor;
set
{
if (!Equals(_focusCueColor, value))
{
_focusCueColor = value;
Refresh();
}
}
}
Color _focusCueColor = default;
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if (Enabled && Focused && (MouseButtons == MouseButtons.None))
{
if (FocusRectangleColor != default)
{
using (Pen dottedPen = new Pen(FocusRectangleColor, 2f))
{
dottedPen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
Rectangle focusRect = this.ClientRectangle;
focusRect.Inflate(-12, -12);
e.Graphics.DrawRectangle(dottedPen, focusRect);
}
}
if (FocusCueColor != default)
{
using (Pen cuePen = new Pen(FocusCueColor, FlatAppearance.BorderSize))
{
Rectangle focusRect = this.ClientRectangle;
focusRect.Inflate(-2, -2);
e.Graphics.DrawRectangle(cuePen, focusRect);
}
}
}
}
}
Testing个
在以下情况下,编写的代码应正确导航:
- [Tab]键用于导航.
- 鼠标用于点击按钮.
- 以编程方式调用
button.Focus()
.
- 从当前聚焦的控件调用
SelectNextControl()
.
ActiveControl
以编程方式设置.
为了举止得体:
- 需要正确设置
TabOrder
个控件
- 对于要标记的控件,
TabStop
属性应为true
,否则为False.
- 该表单必须处于活动状态.
- 应该具有焦点的按钮必须具有最低的制表符索引.
Testbench
下面是我用TabIndex
的值测试这个答案的代码,如VS Menu\View\Tab Order中所示.Clone.使用组合框,可以 Select 定时器模式,以在上面列表中导航的三个"程序化"选项之间循环.
public partial class MainForm : Form
{
enum TimerMode { None, FocusNext, SelectNext, NextActive }
public MainForm()
{
InitializeComponent();
comboBoxTimers.Items.AddRange(Enum.GetValues(typeof(TimerMode)).OfType<object>().ToArray());
comboBoxTimers.SelectedIndex = 0;
comboBoxTimers.SelectionChangeCommitted += async(sender, e) =>
{
_timerMode = TimerMode.None;
await _busy.WaitAsync();
_timerMode = (TimerMode)(comboBoxTimers.SelectedItem ?? TimerMode.None);
BeginInvoke(()=>ActiveControl = button1);
string name;
switch (_timerMode)
{
case TimerMode.FocusNext:
do
{
await Task.Delay(1000);
if (!comboBoxTimers.DroppedDown)
{
name = ActiveControl?.Name ?? string.Empty;
switch (name)
{
case nameof(button1): button2.Focus(); break;
case nameof(button2): button3.Focus(); break;
default: case nameof(button3): button1.Focus(); break;
}
}
} while (_timerMode == TimerMode.FocusNext);
_busy.Release();
break;
case TimerMode.SelectNext:
do
{
await Task.Delay(1000);
if (!comboBoxTimers.DroppedDown)
{
if (ActiveControl != null) SelectNextControl(
ctl: ActiveControl,
forward: true,
tabStopOnly: true,
nested: true,
wrap: true
);
}
} while (_timerMode == TimerMode.SelectNext);
_busy.Release();
break;
case TimerMode.NextActive:
do
{
await Task.Delay(1000);
if (!comboBoxTimers.DroppedDown)
{
name = ActiveControl?.Name ?? string.Empty;
switch (name)
{
case nameof(button1): ActiveControl = button2; break;
case nameof(button2): ActiveControl = button3; break;
default: case nameof(button3): ActiveControl = button1; break;
}
}
} while (_timerMode == TimerMode.NextActive);
_busy.Release();
break;
}
};
}
TimerMode _timerMode = TimerMode.None;
SemaphoreSlim _busy = new SemaphoreSlim(1, 1);
}