创建一个具有完整设计时(design time)支持的自定义数据源控件(DataSourceControl)

原文: 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多行,即使在一个简单的数据源控件,代码也很多,如果更复杂的数据源,你不得不写更多的代码。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值