星期一, 11月 23, 2020

[C#] 控件智能標籤

根據 Walkthrough: Adding Smart Tags to a Windows Forms Component 的筆記文

以 TextBox 為例,點選控件右上角的三角符號,跳出來的 TextBox 工作,就是所謂的智能標籤

[C#] 控件智能標籤-1

要建立智能標籤,要先引用 System.Design.dll 

[C#] 控件智能標籤-2 
 

建立 ColorLabelActionList

DesignerActionItem 類別

類型說明
DesignerActionHeaderItem群組標籤,以粗體顯示
DesignerActionTextItem以一般字型顯示的標籤
DesignerActionPropertyItem與屬性相關聯的面板專案。 也可以顯示
與基礎屬性相關聯的核取記號或屬性編輯器
DesignerActionMethodItem顯示為超連結並與方法相關聯的面板專案
using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;

namespace UCControlDesignerExample
{
    /// <summary>
    /// 自訂智能面板內容
    /// </summary>
    public class ColorLabelActionList : DesignerActionList
    {
        private ColorLabel colorLabel;

        private DesignerActionUIService designerActionUISvc = null;

        public ColorLabelActionList(IComponent component) : base(component)
        {
            this.colorLabel = component as ColorLabel;

            // DesignerActionUIService 可以用來更新智能面板
            this.designerActionUISvc = GetService(typeof(DesignerActionUIService)) as DesignerActionUIService;
        }

        /// <summary>
        /// DesignerActionUIService 使用來更新智能面板,在本範例上 [鎖定顏色] 和 [反轉前後背景顏色] 會用上
        /// </summary>
        private void SmartTagPanelRefresh()
        {
            this.designerActionUISvc.Refresh(this.Component);
        }

        /// <summary>
        /// 取得 ColorLabel Property
        /// </summary>
        /// <param name="propName">Property Name</param>
        /// <returns>PropertyDescriptor</returns>
        private PropertyDescriptor GetPropertyByName(String propName)
        {
            PropertyDescriptor prop = TypeDescriptor.GetProperties(colorLabel)[propName];
            if (null == prop)
                throw new ArgumentException($"ColorLabel 屬性:{propName} 沒有找到");
            else
                return prop;
        }

        #region DesignerActionPropertyItem:智能面版上的控件

        public string Text
        {
            get
            {
                return colorLabel.Text;
            }
            set
            {
                GetPropertyByName(nameof(this.Text)).SetValue(colorLabel, value);
            }
        }

        public Color BackColor
        {
            get
            {
                return colorLabel.BackColor;
            }
            set
            {
                GetPropertyByName(nameof(this.BackColor)).SetValue(colorLabel, value);
            }
        }

        public Color ForeColor
        {
            get
            {
                return colorLabel.ForeColor;
            }
            set
            {
                GetPropertyByName(nameof(this.ForeColor)).SetValue(colorLabel, value);
            }
        }

        public bool LockedColor
        {
            get
            {
                return colorLabel.LockedColor;
            }
            set
            {
                GetPropertyByName(nameof(this.LockedColor)).SetValue(colorLabel, value);
                SmartTagPanelRefresh();
            }
        }
        #endregion

        #region DesignerActionMethodItem:智能面板上的方法
        /// <summary>
        /// 反轉前後背景顏色
        /// </summary>
        public void InvertColors()
        {
            Color currentBackColor = colorLabel.BackColor;
            Color currentForeColor = colorLabel.ForeColor;

            BackColor = currentForeColor;
            ForeColor = currentBackColor;

            SmartTagPanelRefresh();
        } 
        #endregion

        /// <summary>
        /// 智能面板內容
        /// </summary>
        /// <returns></returns>
        public override DesignerActionItemCollection GetSortedActionItems()
        {
            DesignerActionItemCollection items = new DesignerActionItemCollection();

            // DesignerActionHeaderItem 使用
            string Header_Appearance = "外觀";
            string Header_Information = "控件資訊";

            // 定義 Header
            items.Add(new DesignerActionHeaderItem(Header_Appearance));
            items.Add(new DesignerActionHeaderItem(Header_Information));

            items.Add(new DesignerActionPropertyItem(
                            nameof(this.LockedColor), // 綁定的 Property
                            "鎖定顏色", // 文字說明
                            Header_Appearance, // Category:輸入 Header 名稱
                            "鎖定顏色相關屬性" // 提示說明
                            )); 

            if (!LockedColor)
            {
                items.Add(new DesignerActionPropertyItem(
                                 nameof(this.BackColor),
                                 "背景顏色", 
                                 Header_Appearance,
                                 "選擇背景顏色"));

                items.Add(new DesignerActionPropertyItem(
                                 nameof(this.ForeColor),
                                 "前景顏色", 
                                 Header_Appearance,
                                 "選擇前景顏色"));

                // DesignerActionMethodItem 也可以加入 context menu 內 例如:designer verb
                items.Add(new DesignerActionMethodItem(this,
                                 nameof(this.InvertColors), // Method 名稱
                                 "反轉前景和背景顏色",
                                 Header_Appearance,
                                 "反轉前景和背景顏色",
                                  true));
            }

            items.Add(new DesignerActionPropertyItem(
                nameof(this.Text),
                "標籤文字", 
                Header_Appearance,
                "設定標籤文字"));

            // ColorLabel 相關資訊
            items.Add(new DesignerActionTextItem($"位置:{colorLabel.Location}", Header_Information));
            items.Add(new DesignerActionTextItem($"大小:{colorLabel.Size}", Header_Information));

            return items;
        }
    }
}
建立 UserControl:ColorLabel

需在 ColorLabel 上定義 Designer Attribute
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;

namespace UCControlDesignerExample
{
    /// <summary>
    /// 標準控件 Label 延伸,新增 ColorLock Property 來使用
    /// </summary>
    [Designer(typeof(ColorLabelControlDesigner))]
    public class ColorLabel : Label
    {
        public bool LockedColor { get; set; } = false;

        public override Color BackColor
        {
            get
            {
                return base.BackColor;
            }
            set
            {
                if (LockedColor)
                    return;
                else
                    base.BackColor = value;
            }
        }

        public override Color ForeColor
        {
            get
            {
                return base.ForeColor;
            }
            set
            {
                if (LockedColor)
                    return;
                else
                    base.ForeColor = value;
            }
        }
    }
}
建立 DesignControl
using System.ComponentModel.Design;
using System.Security.Permissions;
using System.Windows.Forms.Design;

namespace UCControlDesignerExample
{
    /// <summary>
    /// ColorLabel 的智能標籤面板,必須引用 System.Design.dll
    /// </summary>
    [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
    public class ColorLabelControlDesigner : ControlDesigner
    {
        private DesignerActionListCollection actionLists;

        public override DesignerActionListCollection ActionLists
        {
            get
            {
                if (null == actionLists)
                {
                    // 加入智能面板內容
                    actionLists = new DesignerActionListCollection();
                    actionLists.Add(new ColorLabelActionList(this.Component));
                }
                return actionLists;
            }
        }
    }
}
ColorLabel 使用效果
 
[C#] 控件智能標籤-3

[C#] 控件智能標籤-4

在 ColorLabel 控件上使用滑鼠右鍵,可以看見 [反轉前景和背景顏色] Method 可以使用

[C#] 控件智能標籤-5


文章內提到的重點

總共有五點,只記錄有感的兩點

重點一:更新要透過 PropertyDescriptor 來完成
When a property or method in the class derived from DesignerActionList changes the state of the associated control, these changes should not be made by direct setter calls to the component's properties. Instead, such changes should be made through an appropriately created PropertyDescriptor. This indirect approach ensures that smart-tag undo and UI update actions function correctly.
重點二:DesignerActionUIService.Refresh 來更新智能標籤,一開始閱讀時以為是更新控件效果,一整個誤導自己
You can dynamically update the smart-tag panel by calling DesignerActionUIService.Refresh. This process can be used to dynamically change the contents of the smart-tag panel. In the example, the smart tags concerned with changing colors are conditionally included depending on the state of the LockColors property. This Boolean property is also associated with a smart tag, so the developer can lock or unlock the current color selection, at least through the menu.

沒有留言:

張貼留言