智能在线表单设计器 Web Form Builder

FreeForm : Silverlight Agile Form Engine
  博客园  :: 首页  :: 新随笔  :: 管理

我们将结合FreeForm表单设计器的实际设计来说明。

[设想]

我们的设想是在选择控件的时候,控件的属性通过反射自动体现一个属性表格中,这样可以通过属性表格来更改控件的属性,就像VS.Net的IDE做到的那样。

[分析]

控件属性显示的重点实现代码讲解

我们设计的有2个重要的类
重要的类:PropertyItem
:这个是属性的基础元素,只实现INotifyPropertyChanged接口,PropertyItem的构造函数定义了一个对象实例_instance,将一个反射属性字段_propertyInfo赋值:

public PropertyItem(object instance, object value, PropertyInfo property, bool readOnly)
        {
            _instance = instance;
            _propertyInfo = property;
            _value = value;
            _readOnly = readOnly;

            if (instance is INotifyPropertyChanged)
                ((INotifyPropertyChanged)instance).PropertyChanged += new PropertyChangedEventHandler(PropertyItem_PropertyChanged);
        }

重要的类:PropertyGrid
:这个是自定义的控件,继承自System.Windows.Controls.ContentControl,其中在继承OnApplyTemplate的时候,引用了一个
重要的方法this.ResetObject。

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            this.LayoutRoot = (ScrollViewer)this.GetTemplateChild("LayoutRoot");
            this.MainGrid = (Grid)this.GetTemplateChild("MainGrid");

            loaded = true;

            if (resetLoadedObject)
            {
                resetLoadedObject = false;
                this.ResetObject(this.SelectedObject);
            }
        }

这个ResetObject方法的代码:

        void ResetObject(object obj)
        {
            this.ResetMainGrid();

            int rowCount = this.SetObject(obj);

            if (rowCount > 0)
                AddGridSplitter(rowCount);
        }

SetObject返回一个整数,代表的意思就是传入对象的属性数量,这个SetObject是重要的入口方法,代码如下:

        int SetObject(object obj)
        {
            List<PropertyItem> props = new List<PropertyItem>();
            int rowCount = -1;

            // Parse the objects properties
            props = PropertyGrid.ParseObject(obj);
            var categories = (from p in props
                              orderby p.Category
                              select p.Category).Distinct();

            foreach (string category in categories)
            {

                this.AddHeaderRow(category, ref rowCount);

                var items = from p in props
                            where p.Category == category
                            orderby p.Name
                            select p;

                foreach (var item in items)
                    this.AddPropertyRow(item, ref rowCount);

            }
            return rowCount++;

        }

通过ParseObject组合了PropertyItem的列表
static List<PropertyItem> ParseObject(object objItem)

完整代码:

View Code
  1 #region Using Directives
2 using System;
3 using System.ComponentModel;
4 using System.Linq;
5 using System.Reflection;
6 using SLPropertyGrid.Converters;
7 #endregion
8
9 #region PropertyItem
10 /// <summary>
11 /// PropertyItem hold a reference to an individual property in the propertygrid
12 /// </summary>
13 public sealed class PropertyItem : INotifyPropertyChanged
14 {
15 #region Events
16 /// <summary>
17 /// Event raised when an error is encountered attempting to set the Value
18 /// </summary>
19 public event EventHandler<ExceptionEventArgs> ValueError;
20 /// <summary>
21 /// Raises the ValueError event
22 /// </summary>
23 /// <param name="ex">The exception</param>
24 private void OnValueError(Exception ex)
25 {
26 if (null != ValueError)
27 ValueError(this, new ExceptionEventArgs(ex));
28 }
29 #endregion
30
31 #region Fields
32 private PropertyInfo _propertyInfo;
33 private object _instance;
34 private bool _readOnly = false;
35 #endregion
36
37 #region Constructors
38 /// <summary>
39 /// Constructor
40 /// </summary>
41 /// <param name="instance"></param>
42 /// <param name="property"></param>
43 public PropertyItem(object instance, object value, PropertyInfo property, bool readOnly)
44 {
45 _instance = instance;
46 _propertyInfo = property;
47 _value = value;
48 _readOnly = readOnly;
49
50 if (instance is INotifyPropertyChanged)
51 ((INotifyPropertyChanged)instance).PropertyChanged += new PropertyChangedEventHandler(PropertyItem_PropertyChanged);
52 }
53
54 void PropertyItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
55 {
56 if (e.PropertyName == this.Name)
57 Value = _propertyInfo.GetValue(_instance, null);
58 }
59 #endregion
60
61 #region Properties
62
63 public string Name
64 {
65 get { return _propertyInfo.Name; }
66 }
67
68 public string DisplayName
69 {
70 get
71 {
72 if (string.IsNullOrEmpty(_displayName))
73 {
74 DisplayNameAttribute attr = GetAttribute<DisplayNameAttribute>(_propertyInfo);
75 _displayName = (attr != null) ? attr.DisplayName : Name;
76 }
77
78 return _displayName;
79 }
80 } private string _displayName;
81
82 public string Category
83 {
84 get
85 {
86 if (string.IsNullOrEmpty(_category))
87 {
88 CategoryAttribute attr = GetAttribute<CategoryAttribute>(_propertyInfo);
89 if (attr != null && !string.IsNullOrEmpty(attr.Category))
90 _category = attr.Category;
91 else
92 _category = "Misc";
93 }
94 return this._category;
95 }
96 } private string _category;
97
98 public object Value
99 {
100 get { return _value; }
101 set
102 {
103 if (_value == value) return;
104 object originalValue = _value;
105 _value = value;
106 try
107 {
108 Type propertyType = this._propertyInfo.PropertyType;
109 if (((propertyType == typeof(object)) || ((value == null) && propertyType.IsClass)) || ((value != null) && propertyType.IsAssignableFrom(value.GetType())))
110 {
111 _propertyInfo.SetValue(_instance, value, (BindingFlags.NonPublic | BindingFlags.Public), null, null, null);
112 OnPropertyChanged("Value");
113 }
114 else
115 {
116 try
117 {
118 if (propertyType.IsEnum)
119 {
120 object val = Enum.Parse(_propertyInfo.PropertyType, value.ToString(), false);
121 _propertyInfo.SetValue(_instance, val, (BindingFlags.NonPublic | BindingFlags.Public), null, null, null);
122 OnPropertyChanged("Value");
123 }
124 else
125 {
126 TypeConverter tc = TypeConverterHelper.GetConverter(propertyType);
127 if (tc != null)
128 {
129 object convertedValue = tc.ConvertFrom(value);
130 _propertyInfo.SetValue(_instance, convertedValue, null);
131 OnPropertyChanged("Value");
132 }
133 else
134 {
135 // try direct setting as a string...
136 _propertyInfo.SetValue(_instance, value.ToString(), (BindingFlags.NonPublic | BindingFlags.Public), null, null, null);
137 OnPropertyChanged("Value");
138 }
139 }
140 }
141 catch (Exception ex)
142 {
143 _value = originalValue;
144 OnPropertyChanged("Value");
145 OnValueError(ex);
146 }
147 }
148 }
149 catch (MethodAccessException mex)
150 {
151 _value = originalValue;
152 _readOnly = true;
153 OnPropertyChanged("Value");
154 OnPropertyChanged("CanWrite");
155 OnValueError(mex);
156 }
157 }
158 } private object _value;
159
160 public Type PropertyType
161 {
162 get { return _propertyInfo.PropertyType; }
163 }
164
165 public bool CanWrite
166 {
167 get { return _propertyInfo.CanWrite && !_readOnly; }
168 }
169
170 public bool ReadOnly
171 {
172 get { return _readOnly; }
173 internal set { _readOnly = value; }
174 }
175
176 #endregion
177
178 #region Helpers
179 public static T GetAttribute<T>(PropertyInfo propertyInfo)
180 {
181 var attributes = propertyInfo.GetCustomAttributes(typeof(T), true);
182 return (attributes.Length > 0) ? attributes.OfType<T>().First() : default(T);
183 }
184 public T GetAttribute<T>()
185 {
186 return GetAttribute<T>(_propertyInfo);
187 }
188 #endregion
189
190 #region INotifyPropertyChanged Members
191
192 public event PropertyChangedEventHandler PropertyChanged;
193
194 private void OnPropertyChanged(string propertyName)
195 {
196 if (string.IsNullOrEmpty(propertyName)) throw new ArgumentNullException("propertyName");
197 PropertyChangedEventHandler handler = PropertyChanged;
198 if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
199 }
200
201 #endregion
202 }
203 #endregion

FreeForm控件属性编辑

属性编辑

我们打开一个表单模板,或者自己新建一个模板,拖放一些控件,例如下图:

 

点击其中一个控件,然后打开 Property”页,可以看到控件的属性

 

细节:

 

可以进入一些相关复合属性

比如动作触发规则属性:

 

再比如控件属性

 

 

即时属性修改

 

我们在选择一个控件,修改属性的时候,能够立即在表单设计器中产生效果,比如控件的标签:

 

我们可以即时修改,效果:

 

 

Demo

http://crmwin.com/FreeForm2011TestPage.html

.

我们的网站(昕友软件):http://crmwin.com