65.9K
CodeProject 正在变化。 阅读更多。
Home

创建具有完整设计时支持的自定义 DataSourceControl

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (10投票s)

2007 年 1 月 3 日

8分钟阅读

viewsIcon

65168

downloadIcon

1010

关于创建具有完整设计时支持的 DataSourceControl 的文章。

Sample Image - CustomDataSourceDesigner.png

引言

本文介绍了如何创建自定义 DataSourceControl 并为其添加完整的设计时支持。

背景

本文假定您熟悉 DataSourceControls 并了解设计时基础设施的工作原理。如果不是这样,请查看以下文章。

对于 DataSourceControls

对于设计时基础设施

创建自定义 DataSourceControl

我们将编写的数据源将能够检索数据但不能修改数据。它仅支持 Select 操作。它将类似于 ObjectDataSource,但仅用于检索数据。它将有一个 TypeName 属性来保存类名,以及一个 SelectMethod 属性来保存要在该类中调用的方法。为了避免编写大量代码,我们将仅调用静态方法。我们还将有一个参数集合传递给 SelectMethod (SelectParameters)。我将解释创建 DataSourceControl 时要执行的主要任务,但我不会详细解释方法或属性的作用。代码在复杂区域应该有足够的注释,以便您能够跟上我。

实现 DataSourceControl 时要做的第一件事是选择我们将拥有多少个 DataSourceView 并编写 IDataSource 相关的代码。在此示例中,我们只有一个视图

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

由于 CustomDataSourceView 是执行所有工作的类,因此最佳方法是将属性存储在该类中。但是,我们需要在 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; }
 }

并将此添加到 CustomDataSourceView 类中

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 以支持自定义视图状态管理。在这种情况下,我们使用它来保存 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);
    }
}

我们使用一对来存储视图状态。第一个元素用于存储父视图的状态,第二个元素用于存储视图的状态。对于 CustomDataSourceView,状态管理是

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 中实际选择

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, false, true);
 
    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;
 }

此代码距离生产代码还差得很远。例如,可能存在多个与 SelectMethod 同名但参数不同的方法。参数转换对引用类型和泛型类型处理不佳。不支持 DataSet 和 DataTable 类型,因为它们不实现 IEnumerable。您还需要提取底层的 DataView 来处理它们。但是,添加所有这些“额外功能”会使事情更难理解。

现在我们将为我们的 CustomDataSource 控件创建一个设计器。DataSourceDesigner 需要执行的主要任务是

  • 配置数据源
  • 公开架构信息

此外,我们必须公开至少一个 DesignerDataSourceView。DataSource 控件公开一个或多个 DataSourceView,而 DataSourceDesigner 公开一个或多个 DesignerDataSourceView

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

正如您所看到的,代码与自定义数据源中用于公开自定义数据源视图的代码非常相似。由于我们的数据源仅检索数据,因此 DesignerDataSourceView 的默认实现足以满足所有 CanXXX 属性。为了快速配置我们的自定义 DataSource,我们将提供一个 GUI,允许我们使用下拉列表选择 TypeName 和 SelectMethod

Design time

为了能够显示“配置数据源”对话框,我们需要重写 CanConfigure 属性并实现 Configure 方法

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

由于 GUI 将一次更改多个属性,因此我们必须创建事务性更改以提供撤消功能。该窗体使用类型发现服务而不是反射来填充第一个下拉列表,其中包含所有可用类型。为什么?因为使用反射,我们只能获取已编译程序集的所有类型。但是,我们可以在未编译项目的情况下添加更多类型。我们还可以有不编译的类型,类型发现服务也会显示它们。因此,使用类型发现服务而不是反射要好得多。

在代码中,我们没有删除很可能不是 TypeName 属性候选类型的类型——即泛型类型、接口——以使代码尽可能简单

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 属性时,下拉列表中的选定方法将是第一个名称与 SelectMethod 相同的。为了简化代码,不对参数进行检查,但对于生产代码,您可能需要检查参数是否匹配。

在 FillMethods 方法中,使用 GetType 方法获取类型,该方法使用解析服务。这是因为我们之前指定使用类型发现服务的原因。为了简化代码,我们没有删除一些肯定不是正确方法的函数,例如属性的 getter 和 setter 或抽象方法。

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, false, true);
}

当用户在“配置数据源”窗体中单击“确定”按钮时,执行的代码是

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 并刷新架构。要提供架构信息,我们必须在 CanRefreshSchema 方法中返回 true,并实现 RefreshSchema 方法。当我们提供架构信息时,控件可以提供字段选择器——例如 GridView 的列——并基于架构信息生成模板,例如绑定到我们的数据源控件的 DataList。但是,我们不能为 CanRefreshSchema 返回 true,因为只有在用户配置了数据源后,我们才能返回架构信息

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 方法,我们需要提取架构信息并生成 SchemaRefreshed 事件。如果数据源控件可以提供架构信息,则架构信息将从底层 DesignerDataSourceView 的 Schema 属性中检索。但是,SchemaRefreshed 事件不一定每次都触发,只有在数据源返回不同架构时才触发。要了解其重要性,请考虑:如果数据源绑定到 GridView,每次触发 RefreshSchema 事件时,设计器都会询问是否需要重新生成列和数据键。因此,我们只对架构发生变化时触发 SchemaRefreshed 事件感兴趣。我们使用设计器状态来存储之前的架构。当调用 RefreshSchema 方法时,我们将检查架构是否发生变化,仅在此情况下触发 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);
            }
        }
    }
 }

如您所见,我们获取 SelectMethod 的 MethodInfo 并获取返回类型。所有暴露架构信息的繁重工作都由框架帮助类 TypeSchema 完成。有关 TypeSchema 类的更多信息,请参阅开头的文章。DesignerDataSource 视图公开保存的架构

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

需要澄清的最后一件事是我们重写了 CustomDataSourceDesigner 类中的 PreFilterProperties 方法,以修改 TypeName 和 SelectMethod 属性的工作方式。这是因为当这些属性中的任何一个发生变化时,底层数据源和架构很可能会发生变化。因此,我们必须将其通知给关联的设计器

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();
        }
    }
}

该设计器和数据源控件的完整源代码可在本文的下载文件中找到。正如您所看到的,为数据源控件添加设计时支持并不十分复杂,但您需要编写相当多的代码——在此示例中为 1300 行——即使对于简单的数据源也是如此。您的数据源越复杂,您需要编写的代码就越多。

关注点

本文涵盖的设计时支持是最常见的场景:数据源控件在运行时不渲染任何 HTML,并且仅提供一个窗体来配置数据源。但是,数据源控件有时也可以渲染 HTML——请参阅 PagerDataSource——它不仅是数据提供者,还是数据使用者。如果您想使用数据源控件渲染 HTML,您还有很多工作要做,因为框架没有能够同时渲染 HTML 的数据源控件的基类。

历史

  • 01/03/2007 - 初始版本
  • 06/19/2007 - 文章已编辑并移至 CodeProject.com 主文章库
© . All rights reserved.