Windows Develop Bookmark and Share   
 index > Windows Forms Designer > Displaying list of typed datasets
 

Displaying list of typed datasets

As part of my design-time experience, I would like to display a drop down of all strongly-typed datasets that have been added to the current project. Is it possible to inspect my project and determine the class of each strongly-typed dataset that is a member?

If it's not possible to do this, how does the Windows Form designer determine the class name of a dataset dropped onto the form?

Thanks for any help!
Justin Bailey  Monday, June 01, 2009 4:58 PM
Bruce,

Sorry for the delay. I ended up using another method entirely. Your code helped me on the way but was not the solution I ended up using. One problem with the code you gave is that the currently executing assembly is not my project - it is the package in which my designers are hosted.


I ended ended up solving the problem by breaking it into two steps:
  1. Finding .xsd's in the project
  2. Determine the type generated by the XSD.
I solved #1 using the DTE object. I enumerated all ProjectItem objects in the current project, searching for those with the "Extension" and "CustomTool" properties. This is the LINQ query I used to search the current project for XSDs:

public static IEnumerable<ProjectItem> FindDataSetSchemas(this Project currProject)
{
// Find all XSDs that will generate strongly typed datasets.
var datasets = from dataset in currProject.ProjectItems.AllItems()
let properties = dataset.Properties.OfType<Property>()
// Only select project items that have Extension = .xsd
// and CustomTool = "MSDataSetGenerator". This will
// give us the project items which will generate
// strongly-typed data sets.
let hasExtension = properties.Any((p) => p.Name == "Extension" &&
String.Compare(".xsd", p.Value.IfNotNull((v) => v.ToString(), String.Empty), true) == 0)
let hasCustomTool = properties.Any((p) => p.Name == "CustomTool" &&
String.Compare("MSDataSetGenerator", p.Value.IfNotNull((v) => v.ToString(), String.Empty), true) == 0)
where hasExtension && hasCustomTool
select dataset;
return datasets;
}

I solved the second problem with the BuildManager property on the VSProject2 interface. BuilderManager has a "BuildDesignTimeOutput" method which will create a temporary assembly for inspection. This was trickier as I had 3 sub-problems to solve:

a. Find the .cs file which implements the strongly typed data set.
b. Find the type defined in that file.
c. Build the assembly and extract the right type.

I solved a) by iterating over the ProjectItems elements of the XSD found above. The "CustomToolOutput" property on that XSD indicates which .cs file implements the data set, so I just search the ProjectItem elements until I find the right one.

I solved b) through the FileCodeModel property on the ProjectItem that I foudn in a). Fortunately, I just needed to look for a class that derived from DataSet, which the CodeClass2 interface has support for.

c) was solved by using BuildDesignTimeOutput to build the file found in a). This method returns an XML string that indicates where the file is found. I couldn't find an example of its use anywhere else, but it was easy to parse. I used this LINQ query to build the path to the assembly generated (where results is the string returned:

string results = mgr.BuildDesignTimeOutput(xsdDesigner.Name);
XElement element = XElement.Parse(results);
string assemblyPath = (from e1 in element.Descendants("Application")
from e2 in element.Descendants("Assembly")
select Path.Combine(e1.Attribute("private_binpath").Value,
e2.Attribute("codebase").Value)).FirstOrDefault();

Finally, I searched the generated assembly for the type I discovered in b). By repeating this process for each XSD in the project, I was able to discover all the types exposed.

If there is a better or less convoluted way to do it, I'd love to know!

Justin
Justin Bailey  Friday, June 05, 2009 3:10 PM
Hi Justin,

Actually, we can take advantage of the functionality on the fly. As you can see, when we click the DataSource property of the DataGridView in the propertyGrid or in the smart tag, it will list all the datasources which we can choose. To achievethis, we only need to apply AttributeProviderAttribute for the property.

Here I am writing the following code for your reference.

    public partial class UC1 : UserControl
    {
        public object dataSource;
        public UC1()
        {
            InitializeComponent();
        }
        [AttributeProvider(typeof(IListSource))]
        public object DataSource
        {
            get
            {
                return dataSource;
            }
            set
            {
                dataSource = value;
            }
        }
    }

If you have any doubts on this, please feel free to let me know.

Best regards,
Bruce Zhou
Please mark the replies as answers if they help and unmark if they don't.
Bruce.Zhou  Tuesday, June 02, 2009 9:12 AM
Thanks for your reply. I would like to only display "Project Data Sources", and I'd rather take control of the myself. That is, instead of the Visual Studio-provided picker, I'd like to use my listbox. Are either of those possible?


Hi Justin,

Actually, we can take advantage of the functionality on the fly. As you can see, when we click the DataSource property of the DataGridView in the propertyGrid or in the smart tag, it will list all the datasources which we can choose. To achievethis, we only need to apply AttributeProviderAttribute for the property.


Justin Bailey  Tuesday, June 02, 2009 2:46 PM
Hi Justin,

OK, You can try to use Reflection APIs to get all the fields of all types in the assembly. If the filed type is subclass of DataSet, then it's a datasource member.

Since you want to show all the datasets in the ListBox, we can create a control derived from ListBox, and create a custom Designer for the extended ListBox in which we will populate with datasets.

I'm writing the following code to demonstrate my ideas:

[DesignerAttribute(typeof(UC1ControlDesginer))]
public partial class ListBoxEx : ListBox
{
public ListBoxEx()
{
InitializeComponent();

}
}
public class UC1ControlDesginer : System.Windows.Forms.Design.ControlDesigner
{
ListBox parentControl;
IDesignerHost host;

public override void Initialize(IComponent component)
{
base.Initialize(component);
host = this.Control.Container as IDesignerHost;
if (host != null)
{
host.TransactionClosing += new DesignerTransactionCloseEventHandler(host_TransactionClosing);
}
PopulateDataSourceItems();
}

private void PopulateDataSourceItems()
{
if (this.Control != null)
{
Assembly a = Assembly.GetExecutingAssembly();
parentControl = this.Control as ListBox;
parentControl.Items.Clear();
foreach (Type t in a.GetExportedTypes())
{
FieldInfo[] fi = t.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
foreach (FieldInfo f in fi)
{
if (f.FieldType.IsSubclassOf(typeof(DataSet)))
{
parentControl.Items.Add(f.Name);
}
}
}
}

}
void host_TransactionClosing(object sender, DesignerTransactionCloseEventArgs e)
{
PopulateDataSourceItems();
}
}

When you drag a new instance of the strongly typed dataset to the form, you need to rebuild the project, and manually cause the a transaction which will refresh the ListBox.

Please let me know if you have any problem with the code.

Best regards,
Bruce Zhou



Please mark the replies as answers if they help and unmark if they don't.
Bruce.Zhou  Wednesday, June 03, 2009 3:40 AM
Hi Justin,

Did you any problem when using the code? If you have any problem, please feel free to let me know.


Best regards,
Bruce Zhou
Please mark the replies as answers if they help and unmark if they don't.
Bruce.Zhou  Friday, June 05, 2009 12:17 PM
Bruce,

Sorry for the delay. I ended up using another method entirely. Your code helped me on the way but was not the solution I ended up using. One problem with the code you gave is that the currently executing assembly is not my project - it is the package in which my designers are hosted.


I ended ended up solving the problem by breaking it into two steps:
  1. Finding .xsd's in the project
  2. Determine the type generated by the XSD.
I solved #1 using the DTE object. I enumerated all ProjectItem objects in the current project, searching for those with the "Extension" and "CustomTool" properties. This is the LINQ query I used to search the current project for XSDs:

public static IEnumerable<ProjectItem> FindDataSetSchemas(this Project currProject)
{
// Find all XSDs that will generate strongly typed datasets.
var datasets = from dataset in currProject.ProjectItems.AllItems()
let properties = dataset.Properties.OfType<Property>()
// Only select project items that have Extension = .xsd
// and CustomTool = "MSDataSetGenerator". This will
// give us the project items which will generate
// strongly-typed data sets.
let hasExtension = properties.Any((p) => p.Name == "Extension" &&
String.Compare(".xsd", p.Value.IfNotNull((v) => v.ToString(), String.Empty), true) == 0)
let hasCustomTool = properties.Any((p) => p.Name == "CustomTool" &&
String.Compare("MSDataSetGenerator", p.Value.IfNotNull((v) => v.ToString(), String.Empty), true) == 0)
where hasExtension && hasCustomTool
select dataset;
return datasets;
}

I solved the second problem with the BuildManager property on the VSProject2 interface. BuilderManager has a "BuildDesignTimeOutput" method which will create a temporary assembly for inspection. This was trickier as I had 3 sub-problems to solve:

a. Find the .cs file which implements the strongly typed data set.
b. Find the type defined in that file.
c. Build the assembly and extract the right type.

I solved a) by iterating over the ProjectItems elements of the XSD found above. The "CustomToolOutput" property on that XSD indicates which .cs file implements the data set, so I just search the ProjectItem elements until I find the right one.

I solved b) through the FileCodeModel property on the ProjectItem that I foudn in a). Fortunately, I just needed to look for a class that derived from DataSet, which the CodeClass2 interface has support for.

c) was solved by using BuildDesignTimeOutput to build the file found in a). This method returns an XML string that indicates where the file is found. I couldn't find an example of its use anywhere else, but it was easy to parse. I used this LINQ query to build the path to the assembly generated (where results is the string returned:

string results = mgr.BuildDesignTimeOutput(xsdDesigner.Name);
XElement element = XElement.Parse(results);
string assemblyPath = (from e1 in element.Descendants("Application")
from e2 in element.Descendants("Assembly")
select Path.Combine(e1.Attribute("private_binpath").Value,
e2.Attribute("codebase").Value)).FirstOrDefault();

Finally, I searched the generated assembly for the type I discovered in b). By repeating this process for each XSD in the project, I was able to discover all the types exposed.

If there is a better or less convoluted way to do it, I'd love to know!

Justin
Justin Bailey  Friday, June 05, 2009 3:10 PM

You can use google to search for other answers

Custom Search

More Threads

• Advanced Application Tracing Question
• extending the VS2005 MenuStrip designer
• forms designer error, vb .net 2005
• How to show a context menu for a component in a designer host?
• User control-Design time support
• How to manipulate cells in a TableLayoutPanel
• Licensing
• Adding charts
• Custom Property Editor Woes
• Virtual ListView Control