Program,Life,Society.....

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
原文:Creating a custom DataSourceControl with full design time support

 

Download source files - 13.5 Kb

Download demo project - 26.8 Kb

介绍
这篇文章展示了如何创建一个自定义的数据源控件和怎样给它增加完整的设计时支持

背景
这篇文章假定你已经很熟悉数据源控件(DataSourceControl)和你知道设计时基础工作原理。如果你对这些知识不了解,关注一下下面的文章。

关于数据源控件

  • Data Source Controls - Under the hood (1/4)
  • Data Source Controls - Under the hood (2/4)
  • Data Source Controls - Under the hood (3/4)
  • Data Source Controls - Under the hood (4/4)
    关于设计时的基础知识
  • Introduction to designers
  • ASP.NET designers. The ControlDesigner class
  • ASP.NET designers. The DataSourceDesigner class

    创建自定义的数据源控件

    我们将要编写的数据源是只可以取得数据,但是不能编辑数据。它只支持SELECT操作。它很像ObjectDataSource,但是只是返回数据。它包含一个TypeName属性,用来保存一个类名,包含一个SelectMethod属性,用来保存类中可以调用的方法。为了避免写过多的代码,我们只找出类中的静态方法。我们还有一个参数集合来传递SelectMethod(SelectParameters).在创建数据源控件的同时我将解释将执行的主要任务,但我不详细解释某方法或者某属性具体做什么。

    要做的第一件事情是当实现一个DataSourceControl时,我们要选择多少DataSourceViews和如何编写与IDataSource有关的方法。在这个例子中,我们只有一个View:

    public class CustomDataSource : DataSourceControl
    {
     
        
    protected static readonly string[] _views = "DefaultView" };
     
        
    protected CustomDataSourceView _view;
     
     
        
    protected override DataSourceView GetView(string viewName)
        
    {
            
    if ((viewName == null|| ((viewName.Length != 0&& 
                (String.Compare(viewName, 
    "DefaultView"
                StringComparison.OrdinalIgnoreCase) 
    != 0))) 
            
    {
                
    throw new ArgumentException("An invalid view was requested"
                    
    "viewName");
            }

     
            
    return View;
        }

     
        
    protected override ICollection GetViewNames()
        
    {
            
    return _views;
        }

     
        
    protected CustomDataSourceView View
        
    {
            
    get
            
    {
                
    if (_view == null{
                    _view 
    = new CustomDataSourceView(this, _views[0]);
                    
    if (base.IsTrackingViewState) {
                        ((IStateManager)_view).TrackViewState();
                    }

                }

                
    return _view;
            }

        }

    }

     

    CustomDataSource类作为完成我们全部工作的类,最好我们把需要的属性存储在这里。但是我们需要暴露这些属性在CustomDataSource好让用户去编辑他们在属性编辑器中。因此,我们必须在CustomDataSource类中也添加。

    [Category("Data"), DefaultValue("")]
    public string TypeName
    {
        
    get return View.TypeName; }
        
    set { View.TypeName = value; }
    }

     
    [Category(
    "Data"), DefaultValue("")]
    public string SelectMethod
    {
        
    get return View.SelectMethod; }
        
    set { View.SelectMethod = value; }
    }

     
    [PersistenceMode(PersistenceMode.InnerProperty), Category(
    "Data"), 
        DefaultValue((
    string)null), MergableProperty(false), 
        Editor(
    typeof(ParameterCollectionEditor), 
        
    typeof(UITypeEditor))]
    public ParameterCollection SelectParameters
    {
        
    get return View.SelectParameters; }
     }

    添加这些到CustomDataSouceView类中

    public class CustomDataSourceView : DataSourceView, IStateManager
    {
        
    protected bool _tracking;
        
    protected CustomDataSource _owner;
        
    protected string _typeName;
        
    protected string _selectMethod;
        
    protected ParameterCollection _selectParameters;
     
        
    public string TypeName
        
    {
            
    get
            
    {
                
    if (_typeName == null{
                    
    return String.Empty;
                }

                
    return _typeName;
            }

            
    set
            
    {
                
    if (TypeName != value) {
                    _typeName 
    = value;
                    OnDataSourceViewChanged(EventArgs.Empty);
                }

            }

        }

     
        
    public string SelectMethod
        
    {
            
    get
            
    {
                
    if (_selectMethod == null{
                    
    return String.Empty;
                }

                
    return _selectMethod;
            }

            
    set
            
    {
                
    if (SelectMethod != value) {
                    _selectMethod 
    = value;
                    OnDataSourceViewChanged(EventArgs.Empty);
                }

            }

        }

     
        
    public ParameterCollection SelectParameters
        
    {
            
    get
            
    {
                
    if (_selectParameters == null
                
    {
                    _selectParameters 
    = new ParameterCollection();
                        _selectParameters.ParametersChanged 
    += 
                    
    new EventHandler(ParametersChangedEventHandler);
                    
    if (_tracking) 
                    
    {
                        ((IStateManager)_selectParameters).TrackViewState();
                    }

                }

                
    return _selectParameters;
            }

        }

     
        
    protected void ParametersChangedEventHandler(object o, EventArgs e)
        
    {
            OnDataSourceViewChanged(EventArgs.Empty);
        }

     
        
    public CustomDataSourceView(CustomDataSource owner, string name)
            : 
    base(owner, name)
        
    {
            _owner 
    = owner;
        }

     }

     

     

    注意,当属性改变的时候,OnDataSourceViewChanged 方法被调用去强制重新绑定。同时注意到CustomDataSourceView类实现了IStateManager接口,可以支持定制View 状态管理。既然这样,我们用它去存储SelectParameters。状态管理在CustomDataSource类中这样写:

    protected override void LoadViewState(object savedState)
    {
        Pair previousState 
    = (Pair) savedState;

        
    if (savedState == null
        
    {
            
    base.LoadViewState(null);
        }
     
        
    else 
        
    {
            
    base.LoadViewState(previousState.First);
     
            
    if (previousState.Second != null
            
    {
                ((IStateManager) View).LoadViewState(previousState.Second);
            }

        }

    }

     
    protected override object SaveViewState()
    {
        Pair currentState 
    = new Pair();
     
        currentState.First 
    = base.SaveViewState();
     
        
    if (_view != null
        
    {
            currentState.Second 
    = ((IStateManager) View).SaveViewState();
        }

     
        
    if ((currentState.First == null&& (currentState.Second == null)) 
        
    {
            
    return null;
        }

     
            
    return currentState;
    }

     
    protected override void TrackViewState()
    {
        
    base.TrackViewState();
     
        
    if (_view != null
        
    {
            ((IStateManager) View).TrackViewState();
        }

    }

     
    protected override void OnInit(EventArgs e)
    {
        
    base.OnInit(e);
     
        
    // handle the LoadComplete event to update select parameters
        if (Page != null
        
    {
            Page.LoadComplete 
    += new EventHandler(UpdateParameterValues);
        }

    }

    我们用Pair去存储状态,第一个对象用来存取父视图状态,第二个对象用来存储当前视图的视图状态。关于CustomDataSouceView,状态管理这样写:

    bool IStateManager.IsTrackingViewState
    {
        
    get    return _tracking; }
    }

     
    void IStateManager.LoadViewState(object savedState)
    {
        LoadViewState(savedState);
    }

     
    object IStateManager.SaveViewState()
    {
        
    return SaveViewState();
    }

     
    void IStateManager.TrackViewState()
    {
        TrackViewState();
    }

     
    protected virtual void LoadViewState(object savedState)
    {
        
    if (savedState != null
        
    {
            
    if (savedState != null)
            
    {
                ((IStateManager)SelectParameters).LoadViewState(savedState);
            }

        }

    }

     
    protected virtual object SaveViewState()
    {
        
    if (_selectParameters != null)
        
    {
            
    return ((IStateManager)_selectParameters).SaveViewState();
        }
     
        
    else 
        
    {
            
    return null;
        }

    }

     
    protected virtual void TrackViewState()
    {
        _tracking 
    = true;
     
        
    if (_selectParameters != null)    
        
    {
            ((IStateManager)_selectParameters).TrackViewState();
        }

     }

    我们必须求出SelectParameters的值在每一次请求,因为如果参数改变,我们被需重新绑定:

    protected override void OnInit(EventArgs e)
    {
        
    base.OnInit(e);
     
        
    // handle the LoadComplete event to update select parameters
        if (Page != null
        
    {
            Page.LoadComplete 
    += new EventHandler(UpdateParameterValues);
        }

    }

     
    protected virtual void UpdateParameterValues(object sender, EventArgs e)
    {
        SelectParameters.UpdateValues(Context, 
    this);
    }

    最后我们要做的就是完成在CustomDataSourceView确切的选取部分:

    <PRE lang=cs id=pre6 style="MARGIN-TOP: 0px">protected override IEnumerable ExecuteSelect(
        DataSourceSelectArguments arguments)
    {
        
    // if there isn't a select method, error
        if (SelectMethod.Length == 0
        
    {
            
    throw new InvalidOperationException(
                _owner.ID 
    + ": There isn't a SelectMethod defined");
        }

     
        
    // check if we support the capabilities the data bound control expects
        arguments.RaiseUnsupportedCapabilitiesError(this);
     
        
    // gets the select parameters and their values
        IOrderedDictionary selParams = 
            SelectParameters.GetValues(System.Web.HttpContext.Current, _owner);
     
        
    // gets the data mapper
        Type type = BuildManager.GetType(_typeName, falsetrue);
     
        
    if (type == null
        
    {
            
    throw new NotSupportedException(_owner.ID + ": TypeName not found!");
        }

     
        
    // gets the method to call
        MethodInfo method = type.GetMethod(SelectMethod, 
            BindingFlags.Public 
    | BindingFlags.Static);
     
        
    if (method == null
        
    {
            
    throw new InvalidOperationException(
                _owner.ID 
    + ": SelectMethod not found!");
        }

     
        
    // creates a dictionary with the parameters to call the method
        ParameterInfo[] parameters = method.GetParameters();
        IOrderedDictionary paramsAndValues 
    = 
            
    new OrderedDictionary(parameters.Length);
     
        
    // check that all parameters that the method needs are 
        
    // in the SelectParameters
        foreach (ParameterInfo currentParam in parameters) 
        
    {
            
    string paramName = currentParam.Name;
     
            
    if (!selParams.Contains(paramName)) 
            
    {
                
    throw new InvalidOperationException(_owner.ID + 
                    
    ": The SelectMethod doesn't have a parameter for " + 
                    paramName);
            }

        }

     
        
    // save the parameters and its values into a dictionary
        foreach (ParameterInfo currentParam in parameters) 
        
    {
            
    string paramName = currentParam.Name;
            
    object paramValue = selParams[paramName];
     
            
    if (paramValue != null
            
    {
                
    // check if we have to convert the value
                
    // if we have a string value that needs conversion
                if (!currentParam.ParameterType.IsInstanceOfType(paramValue) && 
                    (paramValue 
    is string)) 
                
    {
     
                    
    // try to get a type converter
                    TypeConverter converter = 
                        TypeDescriptor.GetConverter(currentParam.ParameterType);
                    
    if (converter != null
                    
    {
                        
    try 
                        
    {
                            
    // try to convert the string using the type converter
                            paramValue = converter.ConvertFromString(null
                                System.Globalization.CultureInfo.CurrentCulture, 
                                (
    string)paramValue);
                        }
     
                        
    catch (Exception) 
                        
    {
                            
    throw new InvalidOperationException(
                                _owner.ID 
    + ": Can't convert " + 
                                paramName 
    + " from string to " + 
                                currentParam.ParameterType.Name);
                        }

                    }

                }

            }

     
            paramsAndValues.Add(paramName, paramValue);
        }

     
        
    object[] paramValues = null;
     
        
    // if the method has parameters, create an array to 
        
    // store parameters values
        if (paramsAndValues.Count > 0
        
    {
            paramValues 
    = new object[paramsAndValues.Count];
            
    for (int i = 0; i < paramsAndValues.Count; i++
            
    {
                paramValues[i] 
    = paramsAndValues[i];
            }

        }

     
        
    object returnValue = null;
     
        
    try 
        
    {
            
    // call the method
            returnValue = method.Invoke(null, paramValues);
        }
     
        
    catch (Exception e) 
        
    {
            
    throw new InvalidOperationException(
                _owner.ID 
    + ": Error calling the SelectMethod", e);
        }


        
    return (IEnumerable)returnValue;
     }
    </PRE>

    这些代码远不是产品化的代码,例如,它们可能有重载好几种同样的SelectMethod,使用不同的参数。参数转换不处理Reference和generic类型。也不支持DataSet和DataTable类型,因为它们不是实现的IEnumerable。你也许要在DataView下面去扩展它们。不管怎样,添加这些“扩展特性”会使得代码复杂,更加难理解。

    下载我们来为我们的CustomDataSouce控件来创建设计器。主要的工作是完成DataSouceDesigner,包括:
    配置数据源
    暴露架构(Schema)信息

    因此,我们需要暴露至少一个DesignerDataSouceView。一个DataSouce控件要暴露一个或者多个DataSourceView,一个DataSouceDesigner要暴露一个或多DesignerDataSouceViews:

    private static readonly string[] _views = "DefaultView" };
     
    public override DesignerDataSourceView GetView(string viewName)
    {
        
    if ((viewName == null|| ((viewName.Length != 0&& 
            (String.Compare(viewName, 
    "DefaultView"
            StringComparison.OrdinalIgnoreCase) 
    != 0)))
        
    {
            
    throw new ArgumentException("An invalid view was requested"
                
    "viewName");
        }

     
        
    return View;
    }

     
    public override string[] GetViewNames()
    {
        
    return _views;
    }

    你看到了,代码和我们写过的在Data Souce中暴露DataSouce view的代码很相似。因为我们的数据源只返回数据,默认DesignerDataSourceView实现就可以满足全部的CanXXX属性。为了快速的配置我们的自定义的数据源,我们提供了一个图形界面(GUI)可以让我们从下拉框中选取TypeName和SelectMethod:

    为了可以显示配置数据源对话窗口,我们必须重载CanConfigure属性和实现Congigure方法:

    <PRE lang=cs id=pre8 style="MARGIN-TOP: 0px">public override bool CanConfigure
    {
        
    get return true; }
    }

     
    public override void Configure()
    {
        _inWizard 
    = true;
     
        
    // generate a transaction to undo changes
        InvokeTransactedChange(Component, 
            
    new TransactedChangeCallback(ConfigureDataSourceCallback), 
            
    null"ConfigureDataSource");
        _inWizard 
    = false;
    }

     
    protected virtual bool ConfigureDataSourceCallback(object context)
    {
        
    try 
        
    {
            SuppressDataSourceEvents();
     
            IServiceProvider provider 
    = Component.Site;
            
    if (provider == null)
            
    {
                
    return false;
            }

     
            
    // get the service needed to show a form
            IUIService UIService = 
                (IUIService) provider.GetService(
    typeof(IUIService));
            
    if (UIService == null)
            
    {
                
    return false;
            }

     
            
    // shows the form
            ConfigureDataSource configureForm = 
                
    new ConfigureDataSource(provider, this);
            
    if (UIService.ShowDialog(configureForm) == DialogResult.OK)
            
    {
                OnDataSourceChanged(EventArgs.Empty);
                
    return true;
            }

        }
     
        
    finally 
        
    {
            ResumeDataSourceEvents();
        }

        
    return false;
     }
    </PRE>

    当GUI界面同时将要更改几个属性时,我们必须要建立一个事务改变来提供撤销功能。我们用一个type discovery服务代替反射机制来获得全部的类型填充窗口界面上的第一个下来框。为什么呢?因为用反射机制,我们只能得到全部已经编译的程序集类型,现在是我们要可以添加更多的类型即使还没有在工程中被编译。我们用type Discovery服务可以得到这些没有编译的并且显示他们,因此,用type discovery服务来代替反射要好的多。

    在代码中,我没有移除那些可能对于我们的TypeName属性不能使用的,例如generic类型,接口--这样做是为了使代码尽可能简单:

    private void DiscoverTypes()
    {
        
    // try to get a reference to the type discovery service
        ITypeDiscoveryService discovery = null;
        
    if (_component.Site != null
        
    {
            discovery 
    = 
                (ITypeDiscoveryService)_component.Site.GetService(
                
    typeof(ITypeDiscoveryService));
        }

     
        
    // if the type discovery service is available
        if (discovery != null
        
    {
            
    // saves the cursor and sets the wait cursor
            Cursor previousCursor = Cursor.Current;
            Cursor.Current 
    = Cursors.WaitCursor;
     
            
    try 
            
    {
                
    // gets all types using the type discovery service
                ICollection types = discovery.GetTypes(typeof(object), true);
                ddlTypes.BeginUpdate();
     
                ddlTypes.Items.Clear();
      
                
    // adds the types to the list
                foreach (Type type in types) 
                
    {
                    TypeItem typeItem 
    = new TypeItem(type);
                    ddlTypes.Items.Add(typeItem);
                }

            }
     
            
    finally 
            
    {
                Cursor.Current 
    = previousCursor;
                ddlTypes.EndUpdate();
            }

        }

     }

    TypeItem类是用来存储下拉框中的数据类型。当一个类型被选中从第一个下拉框列表,另外一个下拉框列表将得到算中类型的全部方法。

    private void FillMethods()
    {
        
    // saves the cursor and sets the wait cursor
        Cursor previousCursor = Cursor.Current;
        Cursor.Current 
    = Cursors.WaitCursor;
     
        
    try 
        
    {
            
    // gets all public methods (instance + static)
            MethodInfo[] methods = 
                CustomDataSourceDesigner.GetType(_component.Site, TypeName).
                GetMethods(BindingFlags.Public 
    | BindingFlags.Static | 
                BindingFlags.Instance 
    | BindingFlags.FlattenHierarchy);
            ddlMethods.BeginUpdate();
     
            ddlMethods.Items.Clear();
     
            
    // adds the methods to the dropdownlist
            foreach (MethodInfo method in methods) 
            
    {
                MethodItem methodItem 
    = new MethodItem(method);
                ddlMethods.Items.Add(methodItem);
            }

        }
     
        
    finally 
        
    {
            Cursor.Current 
    = previousCursor;
            ddlMethods.EndUpdate();
        }

    }

    为了快速取得和设置TypeName和SelectMethod从窗体上,我们定义了下面的一些属性:

    internal string TypeName
    {
        
    get 
        
    {
            
    // gets the selected type
            TypeItem selectedType = ddlTypes.SelectedItem as TypeItem;
     
            
    // return the selected type
            if (selectedType != null)
            
    {
                
    return selectedType.Name;
            }
     
            
    else 
            
    {
                
    return String.Empty;
            }

        }

        
    set 
        
    {
            
    // iterate through all the types searching for the requested type
            foreach (TypeItem item in ddlTypes.Items)
            
    {
                
    // if we have found it, select it
                if (String.Compare(item.Name, value, true== 0
                
    {
                    ddlTypes.SelectedItem 
    = item;
                    
    break;
                }

            }

        }

    }

     
    internal string SelectMethod
    {
        
    get 
        
    {
            
    // gets the select method
            string methodName = String.Empty;
     
            
    if (MethodInfo != null
            
    {
                methodName 
    = MethodInfo.Name;
            }

     
            
    return methodName;
        }

        
    set    
        
    {
            
    // iterate through all the types searching for the requested type
            foreach (MethodItem item in ddlMethods.Items) 
            
    {
                
    // if we have found it, select it
                if (String.Compare(item.MethodInfo.Name, value, true== 0
                
    {
                    ddlMethods.SelectedItem 
    = item;
                    
    break;
                }

            }

        }

    }

     
    internal MethodInfo MethodInfo
    {
        
    get 
        
    {
            MethodItem item 
    = ddlMethods.SelectedItem as MethodItem;
     
            
    if (item == null
            
    {
                
    return null;
            }

     
            
    return item.MethodInfo;
        }

    }

    注意这里为了简化代码,当SelectMethod属性被设置时,Select Method下拉列表中显示的是第一个方法,但是为了简化代码,认为没有参数被选中,如果作为产品你必须要去检查是参数匹配。

    在FillMethod方法中,type是用GetType方法中获得的,使用的resolution服务。使用这个服务的道理和前面使用type discovery服务的道理是一样的。

    internal static Type GetType(IServiceProvider serviceProvider, 
        
    string typeName)
    {
        
    // try to get a reference to the resolution service
        ITypeResolutionService resolution = 
            (ITypeResolutionService)serviceProvider.
        GetService(
    typeof(ITypeResolutionService));
        
    if (resolution == null
        
    {
            
    return null;
        }

     
        
    // try to get the type
        return resolution.GetType(typeName, falsetrue);
    }

    当用户在配置数据源窗口点击接受按钮,下面的代码将执行:

    private void bOK_Click(object sender, EventArgs e)
    {
        
    // if the type has changed, save it
        if (String.Compare(TypeName, _component.TypeName, false!= 0
        
    {
            TypeDescriptor.GetProperties(
                _component)[
    "TypeName"].SetValue(_component, TypeName);
        }

     
        
    // if the select method has changed, save it
        if (String.Compare(SelectMethod, _component.SelectMethod, false!= 0
        
    {
            TypeDescriptor.GetProperties(
                _component)[
    "SelectMethod"].SetValue(_component, SelectMethod);
        }

     
        
    // if there is method selected, refresh the schema
        if (MethodInfo != null
        
    {
            _designer.RefreshSchemaInternal(MethodInfo.ReflectedType, 
                MethodInfo.Name, 
                MethodInfo.ReturnType, 
    true);
        }

    }

    我们保存Type和SelectMethod并且刷新schema。为了提供schema信息,我们必须在CanRefreshSchema方法中返回true,还要实现RefeshSchema方法。当我们提供Schema信息。控件可以提供字段读取者,例如GridView上的Columns;基于schema信息产生一个模板,例如一个DataList绑定到我们的数据源控件。然而,在这里我们不能在CanRefreshSchema中返回ture,因为我们只有当用户在配置数据源的时候才可以返回schema信息:

    public override bool CanRefreshSchemablic override bool CanRefreshSchema
    {
        
    get    
        
    {
            
    // if a type and the select method have been 
            
    // specified, the schema can be refreshed
            if (!String.IsNullOrEmpty(TypeName) && !String.IsNullOrEmpty(
                SelectMethod)) 
            
    {
                
    return true;
            }
     
            
    else 
            
    {
                
    return false;
            }

       }

    }

    为了实现RefreshSchema方法,我们必须扩展schema信息和产生SchemaRefreshed事件,如果数据源控件可以提供schema信息,schema信息将被在DesignerDataSouceView中的Schema属性中得到。然而,SchemaRefreshed事件不可是随时触发,只有当数据源返回一个不同的schema。为了说明这个的重要性,想想这个:如果数据源绑定到一个GridView上,当每一次RefreshSchema时间被触发,设计器将被询问是否产生Columns和data keys。因此,我们只关心在schema改变的时候触发SchemaRefreshed时间。我们用Designer state保存先前的schema,一旦RefreshSchema方法被调用,我们去检查schema是否改变,当改变的时候我们触发SchemaRefreshed事件。下面的代码涉及到RefreshSchema事件:

    internal IDataSourceViewSchema DataSourceSchema
    {
        
    get 
        

            
    return DesignerState["DataSourceSchema"as IDataSourceViewSchema; 
        }

        
    set 
        

            DesignerState[
    "DataSourceSchema"= value; 
        }

    }


    public override void RefreshSchema(bool preferSilent)
    {
        
    // saves the old cursor
        Cursor oldCursor = Cursor.Current;

        
    try 
        
    {
            
    // ignore data source events while refreshing the schema
            SuppressDataSourceEvents();

            
    try 
            
    {
                Cursor.Current 
    = Cursors.WaitCursor;

                
    // gets the Type used in the DataSourceControl
                Type type = GetType(Component.Site, TypeName);

                
    // if we can't find the type, return
                if (type == null
                
    {
                        
    return;
                }


                
    // get all the methods that can be used as the select method
                MethodInfo[] methods = 
                    type.GetMethods(BindingFlags.FlattenHierarchy 
    |
                    BindingFlags.Static 
    | BindingFlags.Instance | 
                    BindingFlags.Public);

                MethodInfo selectedMethod 
    = null;

                
    // iterates through the methods searching for the select method
                foreach (MethodInfo method in methods) 
                
    {
                    
    // if the method is named as the selected method, select it
                    if (IsMatchingMethod(method, SelectMethod)) 
                    
    {
                        selectedMethod 
    = method;
                        
    break;
                    }

                }


                
    // if the SelectMethod was found, save the type information
                if (selectedMethod != null
                
    {
                    RefreshSchemaInternal(type, selectedMethod.Name,
                    selectedMethod.ReturnType, preferSilent);
                }

            }
     
            
    finally 
            
    {
                
    // restores the cursor
                Cursor.Current = oldCursor;
            }

        }
     
        
    finally 
        
    {
            
    // resume data source events
            ResumeDataSourceEvents();
        }

    }


    internal void RefreshSchemaInternal(Type typeName, 
        
    string method, Type returnType, bool preferSilent)
    {
        
    // if all parameters are filled
        if ((typeName != null&& (!String.IsNullOrEmpty(method)) && 
            (returnType 
    != null)) 
        
    {
            
    try 
            
    {
                
    // gets the old schema
                IDataSourceViewSchema oldSchema = DataSourceSchema;

                
    // gets the schema of the return type
                IDataSourceViewSchema[] typeSchemas = 
                    
    new TypeSchema(returnType).GetViews();

                
    // if we can't get schema information from the type, exit
                if ((typeSchemas == null|| (typeSchemas.Length == 0))
                
    {
                    DataSourceSchema 
    = null;
                    
    return;
                }


                
    // get a view of the schema
                IDataSourceViewSchema newSchema = typeSchemas[0];

                
    // if the schema has changed, raise the schema refreshed event
                if (!DataSourceDesigner.ViewSchemasEquivalent(
                    oldSchema, newSchema))
                
    {
                    DataSourceSchema 
    = newSchema;
                    OnSchemaRefreshed(EventArgs.Empty);
                }

            }
     
            
    catch (Exception e)
            
    {
                
    if (!preferSilent)
                
    {
                    ShowError(DataSourceComponent.Site, 
                        
    "Cannot retrieve type schema for " +
                        returnType.FullName 
    + "" + e.Message);
                }

            }

        }

     }

     

    你可以看到,我们得到MethodInfo从SelectMethod并且得到返回的type。暴露schema信息的困难工作已经由Framework的TypeSchema类完成了.DesignerSource view暴露了保存的schmea:

    public override IDataSourceViewSchema Schema
    {
        
    get 
        
    {
            
    // if a type and the select method have been 
            
    // specified, the schema information is available
            if (!String.IsNullOrEmpty(_owner.TypeName) && !String.IsNullOrEmpty(
                _owner.SelectMethod)) 
            
    {
                
    return _owner.DataSourceSchema;
            }
     
            
    else 
            
    {
                
    return null;
            }

        }

    }

    最后要澄清的事情是我们重载了PreFilterProperties方法在CustomDataSouceDesigner类中,这是为了更正如何让TypeName和SelectMethod属性工作。这是因为这些属性改变的时候在data souce和schema下的属性也会改变。因此,我们必须通告相关联的设计器。

    protected override void PreFilterProperties(IDictionary properties)
    {
        
    base.PreFilterProperties(properties);

        
    // filters the TypeName property
        PropertyDescriptor typeNameProp = 
            (PropertyDescriptor)properties[
    "TypeName"];
        properties[
    "TypeName"= TypeDescriptor.CreateProperty(base.GetType(), 
            typeNameProp, 
    new Attribute[0]);

        
    // filters the SelectMethod property
        PropertyDescriptor selectMethodProp = 
            (PropertyDescriptor)properties[
    "SelectMethod"];
        properties[
    "SelectMethod"= 
            TypeDescriptor.CreateProperty(
    base.GetType(), 
            selectMethodProp, 
    new Attribute[0]);
    }


    public string TypeName
    {
        
    get 
        

            
    return DataSourceComponent.TypeName; 
        }

        
    set    
        
    {
            
    // if the type has changed
            if (String.Compare(DataSourceComponent.TypeName, value, false!= 0)
            
    {
                DataSourceComponent.TypeName 
    = value;

                
    // notify to the associated designers that this 
                
    // component has changed
                if (CanRefreshSchema)
                
    {
                    RefreshSchema(
    true);
                }
     
                
    else 
                
    {
                    OnDataSourceChanged(EventArgs.Empty);
                }

                UpdateDesignTimeHtml();
            }

        }

    }


    public string SelectMethod
    {
        
    get 
        

            
    return DataSourceComponent.SelectMethod; 
        }

        
    set    
        
    {
            
    // if the select method has changed
            if (String.Compare(DataSourceComponent.SelectMethod, 
                value, 
    false!= 0)
            
    {
                DataSourceComponent.SelectMethod 
    = value;

                
    // notify to the associated designers that this 
                
    // component has changed
                if (CanRefreshSchema && !_inWizard)
                
    {
                    RefreshSchema(
    true);
                }
     
                
    else 
                
    {
                    OnDataSourceChanged(EventArgs.Empty);
                }


                UpdateDesignTimeHtml();
            }

        }

    }

    全部的源代码包括设计器和数据源控件都是可以在这里下载的。你可以看到,添加一个为data source控件的设计时支持并不是很可怕的事情,但是你必须写很多的代码,在这个例子中有1300多行,即使在一个简单的数据源控件,代码也很多,如果更复杂的数据源,你不得不写更多的代码。


  • posted on 2007-11-07 11:48  vuejs3  阅读(3202)  评论(5编辑  收藏  举报