[C#.NET][Winform][User Control] 使用 IExtenderProvider 讓 控制項 的焦點切換 有 管理介面
VS內建的TabStop+TabIndex屬性,可以讓我們按Tab鍵就能切換焦點,TabIndex是切換順序,一直以來都覺得這樣的方式不是很方便,一來只有Tab鍵能切換焦點,再者TabIndex的使用不是那麼方便、直覺。
於是我在腦中這樣想,要為控制項新增擴充屬性,Enable是要使用我自訂的功能旗標,IsTabStop則決定是否要停用Tab鍵切換
這個控制項還要能夠,改變切換的順序,以便管理
還要能夠定義按鍵的切換。
這樣的想法完確定後便可開始實作。
@SwitchFocusAdvanFlag.cs
SwitchFlag類別:存放切換旗標
SwitchFocusAdvanParameter類別:這個類別是用來呼叫自定義的控制項,並存放每個控制項的旗標
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing.Design;
namespace System.Windows.Forms.Extend.SwitchFocus
{
public class SwitchSorts : BindingList<Control>
{
public override string ToString()
{
return "SwitchSorts";
}
}
public class SwitchFlags : Dictionary<Control, SwitchFlag>
{
public override string ToString()
{
return "SwitchParameters";
}
}
[TypeConverter(typeof(ExpandableObjectConverter))]
public class SwitchFlag
{
private bool _Enable = false;
[DefaultValue(false)]
public bool Enable
{
get { return _Enable; }
set { _Enable = value; }
}
private bool _IsTabStop = true;
[DefaultValue(true)]
public bool IsTabStop
{
get { return _IsTabStop; }
set { _IsTabStop = value; }
}
public override string ToString()
{
if (this.Enable)
{
return this.Enable.ToString();
}
else
{
return "";
}
}
}
[EditorAttribute(typeof(SwitchFocusAdvanUITypeEditor), typeof(UITypeEditor))]
[DefaultProperty("SwitchSorts")]
public class SwitchFocusAdvanParameter
{
private SwitchFlags _SwitchFlags = new SwitchFlags();
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public SwitchFlags SwitchFlags
{
get { return _SwitchFlags; }
set { _SwitchFlags = value; }
}
private SwitchSorts _SwitchSorts = new SwitchSorts();
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public SwitchSorts SwitchSorts
{
get { return _SwitchSorts; }
set { _SwitchSorts = value; }
}
public override string ToString()
{
return "SwitchSorts";
}
}
}
@SwitchFocusAdvanEditor.cs
SwitchFocusAdvanEditor 類別,是一個使用者控制項,畫面規劃如下,主要是用來改變ListBox(SwitchFocusAdvanParameter.SwitchFlags)的順序
SwitchFocusAdvanUITypeEditor 類別,用來呼叫 SwitchFocusAdvanEditor 控制項,並傳入 SwitchFocusAdvanParameter 類別,實作完成的效果就會像下圖那樣。
using System.ComponentModel;
using System.Drawing.Design;
using System.Windows.Forms.Design;
namespace System.Windows.Forms.Extend.SwitchFocus
{
internal partial class SwitchFocusAdvanEditor : UserControl
{
public SwitchFocusAdvanEditor()
{
InitializeComponent();
}
public SwitchFocusAdvanEditor(SwitchFocusAdvanParameter AdvanParameter)
: this()
{
this.AdvanParameter = AdvanParameter;
}
private SwitchFocusAdvanParameter _AdvanParameter = null;
public SwitchFocusAdvanParameter AdvanParameter
{
get
{
return _AdvanParameter;
}
set
{
if (value == null)
{
enableControls(false);
}
_AdvanParameter = value;
this.SwitchSorts = value.SwitchSorts;
this.SwitchFlags = value.SwitchFlags;
listBox1.DataSource = value.SwitchSorts;
}
}
private SwitchFlags _SwitchFlags = null;
public SwitchFlags SwitchFlags
{
get { return _SwitchFlags; }
set { _SwitchFlags = value; }
}
private SwitchSorts _SwitchSorts = null;
public SwitchSorts SwitchSorts
{
get { return _SwitchSorts; }
set
{
if (value == null || value.Count <= 0)
{
enableControls(false);
}
else
{
enableControls(true);
}
_SwitchSorts = value;
}
}
void enableControls(bool enable)
{
this.buttonMoveDown.Enabled = enable;
this.buttonMoveUp.Enabled = enable;
this.buttonRemove.Enabled = enable;
}
private void SwitchFocusAdvanEditor_Load(object sender, EventArgs e)
{
this.DoubleBuffered = true;
if (this.SwitchSorts == null || this.SwitchSorts.Count <= 0)
{
return;
}
int index = this.listBox1.SelectedIndex;
int count = this.listBox1.Items.Count;
if (index == 0)
{
this.buttonMoveUp.Enabled = false;
}
else if (index == count - 1)
{
this.buttonMoveUp.Enabled = true;
}
}
private void buttonMoveUp_Click(object sender, EventArgs e)
{
int index = this.listBox1.SelectedIndex;
if (index <= 0)
{
return;
}
else
{
move(-1);
}
}
private void buttonMoveDown_Click(object sender, EventArgs e)
{
int index = this.listBox1.SelectedIndex;
int count = this.listBox1.Items.Count;
if (index == count - 1)
{
return;
}
else
{
move(1);
}
}
//上下移動
void move(int offset)
{
int index = this.listBox1.SelectedIndex;
int count = this.listBox1.Items.Count;
Control ctrl = this.SwitchSorts[index];
this.SwitchSorts.Remove(ctrl);
this.SwitchSorts.Insert(index + offset, ctrl);
this.listBox1.SelectedIndex = index + offset;
index = this.listBox1.SelectedIndex;
enableControls(true);
if (index == 0)
{
this.buttonMoveUp.Enabled = false;
}
else if (index == count - 1)
{
this.buttonMoveDown.Enabled = false;
}
}
private void buttonRemove_Click(object sender, EventArgs e)
{
int index = this.listBox1.SelectedIndex;
Control ctrl = this.SwitchSorts[index];
DialogResult result = MessageBox.Show(string.Format("The '{0}' will remote in Box", ctrl.Name), "Ack Message", MessageBoxButtons.YesNo, MessageBoxIcon.Information);
if (result == DialogResult.No)
{
return;
}
this.SwitchSorts.Remove(ctrl);
this.SwitchFlags[ctrl].Enable = false;
this.SwitchFlags[ctrl].IsTabStop = true;
}
}
internal class SwitchFocusAdvanUITypeEditor : UITypeEditor
{
private SwitchFocusAdvanParameter _parameter = null;
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
IWindowsFormsEditorService editorService = null;
if (context != null && context.Instance != null && provider != null)
{
editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
SwitchFocusAdvanParameter parameter = (SwitchFocusAdvanParameter)value;
SwitchFocusAdvanEditor editor = new SwitchFocusAdvanEditor(parameter);
//editor.AdvanParameter = parameter;
editorService.DropDownControl(editor);
this._parameter = editor.AdvanParameter;
}
editorService.CloseDropDown();
return this._parameter;
}
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
if (context != null && context.Instance != null)
{
return UITypeEditorEditStyle.Modal;
//return UITypeEditorEditStyle.DropDown;
//return UITypeEditorEditStyle.None;
}
else
{
return base.GetEditStyle(context);
}
}
public override string ToString()
{
return "SwitchPanel";
}
}
}
完成後在控制項裡就會長這樣
@SwitchFocusAdvanContainer.cs,再來就是要用來擴充控制項屬性的類別 SwitchFocusAdvanContainer
1.[ProvideProperty("SwitchFocus", typeof(Control))] 定義擴充屬性的名稱,所以要實作SetSwitchFocus/GetSwitchFocus
2.NextKeys/PreviousKeys用來決定焦點切換的鍵盤
using System.ComponentModel;
using System.Linq;
namespace System.Windows.Forms.Extend.SwitchFocus
{
[ProvideProperty("SwitchFocus", typeof(Control))]
public partial class SwitchFocusAdvanContainer : Component, IExtenderProvider
{
#region construct
public SwitchFocusAdvanContainer()
{
InitializeComponent();
}
public SwitchFocusAdvanContainer(IContainer container)
: this()
{
container.Add(this);
}
#endregion
#region CanExtend
public bool CanExtend(object target)
{
if (target is Form)
return false;
else
return true;
}
#endregion
#region properties
private Keys[] _NextKeys = new Keys[] { Keys.Enter, Keys.Down, Keys.PageDown };
[Category("Switch Control Focus")]
public Keys[] NextKeys
{
get { return _NextKeys; }
set { _NextKeys = value; }
}
private Keys[] _PreviousKeys = new Keys[] { Keys.Up, Keys.PageUp };
[Category("Switch Control Focus")]
public Keys[] PreviousKeys
{
get { return _PreviousKeys; }
set { _PreviousKeys = value; }
}
#endregion
private SwitchFocusAdvanParameter _AdvanParameter = new SwitchFocusAdvanParameter();
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public SwitchFocusAdvanParameter AdvanParameter
{
get { return _AdvanParameter; }
set { _AdvanParameter = value; }
}
public void SetSwitchFocus(Control Ctrl, SwitchFlag Parameter)
{
if (this.AdvanParameter.SwitchFlags.ContainsKey(Ctrl))
{
this.AdvanParameter.SwitchFlags[Ctrl] = Parameter;
}
else
{
this.AdvanParameter.SwitchFlags.Add(Ctrl, Parameter);
}
if (Parameter.Enable)
{
Ctrl.KeyDown += new KeyEventHandler(Ctrl_KeyDown);
}
else
{
Ctrl.KeyDown -= new KeyEventHandler(Ctrl_KeyDown);
}
Ctrl.TabStop = Parameter.IsTabStop;
}
public SwitchFlag GetSwitchFocus(Control Ctrl)
{
SwitchFlag flag = null;
if (this.AdvanParameter.SwitchFlags.ContainsKey(Ctrl))
{
flag = this.AdvanParameter.SwitchFlags[Ctrl];
}
else
{
flag = new SwitchFlag();
this.AdvanParameter.SwitchFlags.Add(Ctrl, flag);
}
if (flag.Enable && !this.AdvanParameter.SwitchSorts.Contains(Ctrl))
{
this.AdvanParameter.SwitchSorts.Add(Ctrl);
}
else if (!flag.Enable && this.AdvanParameter.SwitchSorts.Contains(Ctrl))
{
this.AdvanParameter.SwitchSorts.Remove(Ctrl);
}
return flag;
}
void Ctrl_KeyDown(object sender, KeyEventArgs e)
{
Control ctrl = null;
int index = findControlIndex(sender as Control);
int count = this.AdvanParameter.SwitchSorts.Count;
if (this.NextKeys.Contains(e.KeyCode))
{
if (index == count - 1)
{
ctrl = this.AdvanParameter.SwitchSorts[0];
}
else
{
ctrl = this.AdvanParameter.SwitchSorts[index + 1];
}
ctrl.Focus();
}
else if (this.PreviousKeys.Contains(e.KeyCode))
{
if (index == 0)
{
ctrl = this.AdvanParameter.SwitchSorts[count - 1];
}
else
{
ctrl = this.AdvanParameter.SwitchSorts[index - 1];
}
}
if (ctrl != null)
{
ctrl.Focus();
}
}
int findControlIndex(Control ctrl)
{
var query = from data in this.AdvanParameter.SwitchSorts
where data.Name == ctrl.Name
let Index = this.AdvanParameter.SwitchSorts.IndexOf(data)
select new { Index };
int count = query.Count();
if (count > 0)
{
return query.First().Index;
}
else
{
return -1;
}
}
}
}
控制項使用步驟
這個控制項已經內建切換下一個控制項焦點:方向鍵下、PageDown、Enter;切換上一個控制項焦點:方向鍵上、PageUp
在專案裡拉一個 SwitchFocusAdvanContainer 控制項,並定義每一個控制項的Enable屬性,按下F5,當焦點再任一控制項內時,便可使用內建鍵盤切換焦點
後記:
程式碼很快就貼完了,這個範例足足花了我近三天的實作,不斷的測試、修改,最終完成想要的功能,或許你看不起這樣的功能,但我心中那份喜悅是有快感的。
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET