0

I am writing a WPF application in C#. This application is design using MVVM.

Currently, I have a parent window with a few check boxes. Use user can check whichever boxes they want and then click the "plot" Button. Once they click "plot", a new child window comes up displaying the data on a single graph.

So, if I have only 1 check box checked, and then click "plot", I will see a graph with a single line on it. If I have 2 check boxes check and click "plot", I will see the same single graph, but it will have 2 lines on it.

My current Implementation:

Currently, I have a "view" class called GraphWindowView. The view obviously needs to know of which data to show. So to do that, I have dependency properties GraphWindowView.Dates and GraphWindowView.Data which ultimatley produces a graph of Data (y axis) vs. Dates (x axis).

Question: This current implementation of GraphWindowView is obviously restricted to only being able to graph one set of data (i.e. Data vs. Dates). I would like to make this (a lot) more extensible and have an arbitrary number of plots available depending on how much check boxes are checked. How would I go about doing this? I think I need to rethink my use of dependency properties...

>>> UPDATE

So I made a GraphLine class which should represent a line on the graph. The "graph" is actually a ChartPlotter element in the GraphWindowPresenter.xaml class. Additionally, I specified a DataType for the GraphLine objects, but that is all I understand. What are the next steps to this, how do I actually add the data to the graph? And how/where do I make instances of GraphLine to populate the ChartPlotter element? Sorry I am pretty lost on this, even after reading quite a few tutorials. Thanks for all the help so far, I really appreciate it!

GraphWindowView.xaml

<Window x:Class="BMSVM_Simulator.View.GraphWindowView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:ViewModel="clr-namespace:BMSVM_Simulator.ViewModel"
        xmlns:d3="http://research.microsoft.com/DynamicDataDisplay/1.0"
        x:Name="ThisGraphWindowInstance"
        Title="Plot" Height="500" Width="750"
        Icon="../res/qualcomm_q_icon.ico.ico"
        MinWidth="400" MinHeight="300">

    <Window.DataContext>
        <ViewModel:GraphWindowPresenter/>
    </Window.DataContext>

    <d3:ChartPlotter Name="plotter" Margin="10,10,20,10">
        <d3:ChartPlotter.HorizontalAxis>
            <d3:HorizontalIntegerAxis Name="dateAxis"/>
        </d3:ChartPlotter.HorizontalAxis>
        <d3:ChartPlotter.VerticalAxis>
            <d3:VerticalIntegerAxis Name="countAxis"/>
        </d3:ChartPlotter.VerticalAxis>

        <d3:Header FontFamily="Arial" Content="{Binding ElementName=ThisGraphWindowInstance, Path=title}"/>
        <d3:VerticalAxisTitle FontFamily="Arial" Content="{Binding ElementName=ThisGraphWindowInstance, Path=yAxis}"/>
        <d3:HorizontalAxisTitle FontFamily="Arial" Content="{Binding ElementName=ThisGraphWindowInstance, Path=xAxis}"/>
    </d3:ChartPlotter>

    <Window.Resources>
        <DataTemplate DataType="{x:Type ViewModel:GraphLine}">
            <!--WHAT GOES HERE-->
        </DataTemplate>
    </Window.Resources>

</Window>

GraphLine.cs

namespace BMSVM_Simulator.ViewModel
    {
        class GraphLine
        {
            public string xAxis                     { get; private set; }
            public string yAxis                     { get; private set; }
            public string title                     { get; private set; }
            public string legend                    { get; private set; }
            public EnumerableDataSource<int> data   { get; private set; }
            public EnumerableDataSource<int> dates  { get; private set; }
        }
    }
Rich E
  • 233
  • 2
  • 10

1 Answers1

3

Most of these types of problems in WPF can be sorted out by some careful use of data binding and DataTemplates, rather than miles of procedural code. The general idea is that you create a custom class with all of the properties that are required to draw all of your lines. You would then declare a DataTemplate to define how the various properties are to be data bound, perhaps a little something like this:

<DataTemplate DataType="{x:Type YourXamlNamespacePrefix:GraphLine}">
    <Line X1="{Binding X1}" Y1="{Binding Y1}" X2="{Binding X2}" Y2="{Binding Y2}" />
</DataTemplate>

Then you create a collection of your custom class instances and data bind it to some collection control, like an ItemsControl and each one will be automatically rendered in the correct location:

<ItemsControl ItemsSource="{Binding YourGraphLineCollection, RelativeSource={
    RelativeSource AncestorType={x:Type YourXamlNamespacePrefix:YourControlName}}}" />

Welcome to the powerful world of WPF data binding and DataTemplates.


UPDATE >>>

The custom class to data bind to the Line elements is not a view model. Think of it as a data type class, for which you will declare a DataTemplate like the one above. When I said that it should have all of the required properties, if you look at the above example, you'll see that it would at least need four double properties to data bind to the four used properties of the Line element. However, you might also choose to add further properties to data bind to the Stroke, StrokeThickness or Fill properties for example.

As for where you should define the DataTemplate, it should be within scope of the items that have it applied. If you want to use it in one view, then put it in the UserControl.Resources section of that view. However, if you want to use the same DataTemplate, then you should put it into the Application.Resources section of the App.xaml file because those Resources are available application wide.


FINAL UPDATE >>>

As noted in my comment, teaching users how to use WPF is definitely out of scope for this website, so I won't be doing that. To learn about DataTemplates, you should read the Data Templating Overview page on MSDN. When you don't know about something, MSDN should always be your first place to search for answers.

I can give you a few last tips before I go: The DependencyProperty in your control should be of type ObservableCollection<GraphLine>. Inside your control, you should data bind them to some sort of ItemsControl as shown above - I changed the Binding Path in it because you should really use a RelativeSource Binding to locate the property in your situation (where YourControlName is the name of your UserControl where you want to draw the Line objects).

Finally, in your view model (that is linked with the view that contains your new UserControl that draws the lines), you'll need a collection property to data bind with the collection in the UserControl, let's say named YourGraphLineCollectionInViewModel:

<YourXamlNamespacePrefix:YourControlName YourGraphLineCollection="{Binding 
    YourGraphLineCollectionInViewModel}" />

It's in this view model that you add the instances of your GraphLine class into the YourGraphLineCollectionInViewModel collection and as long as you have set up your Binding Paths as shown here, they'll appear in your UI within the ItemsControl. I am assuming that you know how to correctly set your DataContext - if not, you can easily find out how to do that online.

Sheridan
  • 68,826
  • 24
  • 143
  • 183
  • Thanks for the answer, I just read a long tutorial on DataTemplates and now I'm trying to understand this approach. When you say I "create a custom class with all the properties required to draw on of your lines" do you mean like a `ViewModel` for my `GraphWindowView` class? Additionally, would the DataTemplate be defined (using .xaml) in my `GraphWindowView.xaml` class or my `MainWindowView.xaml` class? I think I understand the general use of DataTemplates, just not in my specific context/scenario. – Rich E Jun 10 '14 at 17:24
  • Hey, thanks so much for the help so far, I've updated my orignal post and I'm getting there, if you could respond to that it would be awesome. Thanks so much for the help so far! – Rich E Jun 10 '14 at 23:59
  • I've got to be honest... answering these types of questions, where a user keeps adding new requirements and dragging them on and on are quite annoying. In future, please put *all* of your requirements into your question *in the beginning*. It is *not* the job of Stack Overflow answer authors to teach how to use a particular language... questions are *supposed* to have a narrow focus, like 'Can anyone see a bug in this code?', rather than 'Can anyone teach me how to do something?' The reason is simple... the internet is full of tutorials for learning from... this site is not. – Sheridan Jun 11 '14 at 09:11
  • Thank you for all your help. I've read tutorials and I do not understand them in the context of my code. You answers shed a lot of light on the subject, though. Thanks again. – Rich E Jun 11 '14 at 16:12