[C#.NET][Winform][User Control] 使用 IExtenderProvider 讓 控制項 的焦點切換 有 管理介面

[C#.NET][Winform][User Control] 使用 IExtenderProvider 讓 控制項 的焦點切換 有 管理介面

VS內建的TabStop+TabIndex屬性,可以讓我們按Tab鍵就能切換焦點,TabIndex是切換順序,一直以來都覺得這樣的方式不是很方便,一來只有Tab鍵能切換焦點,再者TabIndex的使用不是那麼方便、直覺。

於是我在腦中這樣想,要為控制項新增擴充屬性,Enable是要使用我自訂的功能旗標,IsTabStop則決定是否要停用Tab鍵切換

image

 

這個控制項還要能夠,改變切換的順序,以便管理

image

 

還要能夠定義按鍵的切換。

image

 

這樣的想法完確定後便可開始實作。


@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)的順序
image
 
SwitchFocusAdvanUITypeEditor 類別,用來呼叫 SwitchFocusAdvanEditor 控制項,並傳入 SwitchFocusAdvanParameter 類別,實作完成的效果就會像下圖那樣。
image

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";
        }
    }
}

 

 

完成後在控制項裡就會長這樣

SNAGHTML38bc68b

 


@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,當焦點再任一控制項內時,便可使用內建鍵盤切換焦點

SNAGHTML39c5968



後記:

程式碼很快就貼完了,這個範例足足花了我近三天的實作,不斷的測試、修改,最終完成想要的功能,或許你看不起這樣的功能,但我心中那份喜悅是有快感的。

 

 

若有謬誤,煩請告知,新手發帖請多包涵


Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET

Image result for microsoft+mvp+logo