Declare/Cast variable data type at runtime

I am working on WPF Charts and want the user to choose a chart series type at run time. If the series type is know at design time, I could have simply declared a variable of the series type like this...

ColumnSeries cs = new ColumnSeries();
cs.ItemsSource = source;
Chart1.Series.Add(cs);


Or would have used a generic series variable and used casting;
ISeries is;
((ColumnSeries) is).ItemsSource = source;
Chart1.Series.Add(is);


Now the problem is I am not only adding series at runtime, but also allowing the users to select the Series type. I could use IF/Switch conditions, but that would not be efficient. How can I declare the data type of the Series variable at runtime.
LVL 9
Shahid ThaikaSole ProprietorAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

AndyAinscowFreelance programmer / ConsultantCommented:
>> I could use IF/Switch conditions, but that would not be efficient

I doubt you would notice any performance drop with that
0
Shahid ThaikaSole ProprietorAuthor Commented:
It's not about performance, rather about good coding practice and maintenance. Given there are many types of Series, some even beyond my knowledge, I have to hard code at least six lines for every series type. If I, or someone else, want to make modifications later on, it's going to be a nightmare.
0
mikebirtCommented:
Hi,

I'm not sure i fully understand the what you need to achieve. Is it possible your series could all implement some ISeries interface (as you've suggested) and then you provide a reflected list if ISeries implemenations to the user? is this how the user is to select the series to be added? If so, is this then a question about how to use reflection to parse your assemblies and construct objects of your classes?

Assuming your series implementations are in the executing assembly you could use something like the example below. The 2 methods i've listed will parse the assembly returning your series implementations and then create an instance of the provided type.

HTH

Mike
        private ISeries ConstructSeries(Type seriesType)
        {
            return Activator.CreateInstance(seriesType) as ISeries;
        }
        
        private Type[] LoadSeriesTypes()
        {
            Type seriesInterface = typeof(ISeries);
            Assembly seriesAssembly = Assembly.GetExecutingAssembly();
            return seriesAssembly.GetTypes()
                    .Where(t => t.GetInterface(seriesInterface.Name) != null)
                            .ToArray();
        }

Open in new window

0
Cloud Class® Course: MCSA MCSE Windows Server 2012

This course teaches how to install and configure Windows Server 2012 R2.  It is the first step on your path to becoming a Microsoft Certified Solutions Expert (MCSE).

Shahid ThaikaSole ProprietorAuthor Commented:
Hi Mike, I think you may be on the right track, but my limited .Net exposure has just left me confused. ISeries is the generic data type, and others such as ColumnSeries, LineSeries, BarSeries are specific data types, though all of them share some basic attributes.

I'll try to explain the requirement in another way....

1) I will have a drop down box with the following items {Bar, Column, Line, etc.}

2) If the user selects Bar, I need to declare a variable of type BarSeries, if user selects Column, then I have to declare a variable of type ColumnSeries and so on

3) Finally, I will add it to my chart using: Chart1.Series.Add(Variable)



Things would have been really simple, if I could do something like... Chart1.Series[i].Type = "Bar/Column/Line"... but unfortunately this is not the case... or I don't know how.


Hope I am clearer now...
0
Shahid ThaikaSole ProprietorAuthor Commented:
Oh and I cannot simply declare a generic variable (ISeries) and add it to my chart, it requires me to do a Cast.

So either I have to declare a variable of a specific type at the beginning or perform an appropriate cast at the end.
0
mikebirtCommented:
Hi,

Ok, so the reflection idea is the start point i guess, you can use that to populate your list for your requirement No1. The ConstructSeries method will resolve your requirement No2. The only things not covered are your Series.Add(Variable) from requirement No3 and to display a nice list of friendly names in your combo, rather than type names.

Your series.Add( thing ) method, what is the parameter type? If you are casting these objects to something, could that thing be the base class of all your series implementations? - then the cast would be the same for all. Or could your add method accept an object implementing the ISeries interface as it's parameter?  - then no casting needed

Regarding the combo box, i'd consider 1 of 2 approaches; you could use a naming convention on your serieis class names and string handling, eg ColumnSeriesType, BarSeriesType ect... string handling to replace 'SeriesType' with String.Empty. Secondly, you could use a custom attribute to provide a nice friendly name to the type, then do something like seriesType.GetCustomAttributes(typeof(FriendlyNameAttribute),true) - where FriendlyNameAttribute is a class you've defined which inherits from Attribute.

I think the heart of the issue for me right now is to understand your add method and which side can change, either the Add implementation or the base class of your series implementations?

HTH

Mike
0
Shahid ThaikaSole ProprietorAuthor Commented:
The following is the last few lines of my CreateSeries() sub routine. My code can only add a new column series, but based of a ComboBox, I want to create other types of series.

In the code...
dR ===> DataRow variable
aSeries ===> ArrayList

If your next reply will include a code, to simplify things, add a string variable...

string SeriesType = "Chart"

If (SeriesType  == "Bar")
{
    //do something
}

If (SeriesType  == "Column")
{
    //do something
}
                ColumnSeries cs = new ColumnSeries();
                cs.Title = dR["attribute"].ToString();
                cs.IndependentValueBinding = new Binding("Key");
                cs.DependentValueBinding = new Binding("Value");
                cs.ItemsSource = aSeries.ToArray(typeof(KeyValuePair<string, decimal>));
                chtAFR.Series.Add(cs);

Open in new window

0
Shahid ThaikaSole ProprietorAuthor Commented:
BTW, my code uses "System.Windows.Controls.DataVisualization.Charting;" from http://wpf.codeplex.com/
0
mikebirtCommented:
Hi,

sorry for the delay in getting back to you.

I have just reviewed our thread thus far and i notice you refer to the interface as a generic type and also you say you have limited .Net exposure. There are a couple of ideas we need to be clear here. You should make sure you understand what an interface is, and what a base class can be. You also need to understand reflection and custom attributes in order to understand what i was thinking previously in my suggestion to you.

My question before is about the Add method on Series in your code example. you're passing it cs, what is the parameter type required there? is it ColumnSeries or is it something else?

Re-reading over your question i think we may have already answered it in showing you how to create the series type based on a combo selection, using the Activator code i provided. All you then need to do is add it to your series collection.

In order to configure the new series you could use a if/else or switch structure. however, i think it would be better to use a naming convention and reflection to call a method based on the type, for example, MethodInfo mi = this.GetType().GetMethod( seriesType.Name + "Configuration"); and having methods called ColumnSeriesConfiguration, BarSeriesConfiguration etc...

Previously i was thinking your were goinng to implement the series classes yourself, so the stuff about the custom attributes from earlier was off the mark.

HTH

Mike

      private void AddSeries(Type seriesType) 
        { 
            chtAFR.Series.Add(Activator.CreateInstance(seriesType));
        } 
         
        private Type[] LoadSeriesTypes() 
        { 
            Type seriesInterface = typeof(ISeries); 
            Assembly seriesAssembly = Assembly.GetExecutingAssembly(); 
            return seriesAssembly.GetTypes() 
                    .Where(t => t.GetInterface(seriesInterface.Name) != null) 
                            .ToArray(); 
        }

Open in new window

0
Shahid ThaikaSole ProprietorAuthor Commented:
Hi Mike, thanks for sticking with me. I am actually a functional person experimenting with WPF to create Dashboards for our MS CRM, and my limited .Net knowledge is not making things easy. I need to look up info on reflection and custom attributes.

When I pass a parameter to the Add(variable) method, the intellisense asks me for a variable of type 'ISeries'. In my parlance, I'd call 'ISeries' some sort of a base or general variable type and other variable types such as LineSeries, ColumnSeries, BarSeries, etc. a specialization of the ISeries data type, since when I declare a variable of one of the special types, .Net still accepts it without error. However, depending on the variable type my application shows different chart types. In my first tutorial, I have noticed a series being assigned an ItemsSource like this...

((BarSeries) Chart1.Series[0]).ItemsSource = myItemSource

Here is another code sample... just look at the code in the below question, and not the question itself.
http://www.experts-exchange.com/Microsoft/Development/Microsoft_Programming/WPF_and_Silverlight/Q_25282788.html


I will try to learn those new topics and try out your suggestion. If you could fit your above solution in my case, I'd appreciate that as well.
0
mikebirtCommented:
Hi,

Ok, i've put together i brief but complete code sample of usign reflecttion to get the series classes and then create an instance of each one. there is a method provided for a few of the series types to configure then (the series types the program supports) and they are then added to the chart. i've got no data in there, but it illustrates where you would put that stuff.

I go through all series types found where you would want to put them into a combo and respond to the user selecting them.

I'd suggest you run this code, debug it through and see what goes on as it runs. The important points to understand here i guess are reflection and interfaces.

HTH

Mike
public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();

            Chart c = new Chart();

            Assembly a = Assembly.Load("System.Windows.Controls.DataVisualization.Toolkit");
            Type[] seriesTypes = a.GetTypes().Where(t => t.GetInterface(typeof(ISeries).Name) != null).ToArray();

            // this seriesTypes is the itemSource for the combo box. 

            foreach (Type t in seriesTypes)
            {
                ISeries theSeries = Create(t);
                if (theSeries != null)
                {
                    c.Series.Add(theSeries);
                }
            }
        }

        private ISeries Create(Type t)
        {
            ISeries series = null;
            MethodInfo mi = this.GetType().GetMethod("Configure" + t.Name, BindingFlags.NonPublic | BindingFlags.Instance);

            if (mi != null)
            {
                series = mi.Invoke(this, new object[] { Activator.CreateInstance(t) as ISeries }) as ISeries;
            }

            return series;
        }

        private ISeries ConfigureBarSeries(ISeries series)
        {
            BarSeries bar = series as BarSeries;

            //bar.ItemsSource = ...?

            return bar as ISeries;
        }
        private ISeries ConfigureLineSeries(ISeries series)
        {
            PieSeries pie = series as PieSeries;

            //pie.ItemsSource = ...?

            return pie as ISeries;
        }
        private ISeries ConfigureLineSeries(ISeries series)
        {
            LineSeries line = series as LineSeries;

            //line.ItemsSource = ...?

            return line as ISeries;
        }
    }

Open in new window

0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
Shahid ThaikaSole ProprietorAuthor Commented:
Well, I am not sure how much different is your code than my existing one, where I have multiple switch cases to declare a SeriesType and it to my Chart, but at least I learnt a new concept.
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
.NET Programming

From novice to tech pro — start learning today.