Aks
Aks

Reputation: 5236

What to do with charts that dont support binding?

I'm working with a charting tool that doesn't support binding in the mvvm sense. So what I've decided is that I'd use a kind of messaging(like the MVVM Light's messaging framework)service such that everytime the viewmodel observablecollection is updated, a message is sent which when received adds datapoints to the chart(this will be in the code behind unfortunately). Do you guys see any issues with this plan?

Upvotes: 1

Views: 88

Answers (2)

Have you considered injecting your view into the ViewModel using an interface to maintain separation? I know this breaks MVVM but I've successfully used this on a number of WPF projects. I call it MiVVM or Model Interface-to-View ViewModel.

The pattern is simple. Your Usercontrol should have an interface, call it IView. Then in the ViewModel you have a property with a setter of type IMyView, say

public IMyView InjectedView { set { _injectedView = value; } }

Then in the view you create a dependency property called This

public MyUserControl : IMyView
{
    public static readonly DependencyProperty ThisProperty = 
         DependencyProperty.Register("This", typeof(IMyView), typeof(MyUserControl)); 

    public MyUserControl() 
    {
       SetValue(ThisProperty, this);
    } 
    public IMyView This { get { return GetValue(ThisProperty); } set { /* do nothing */ } } 
}

finally in Xaml you can inject the view directly into the ViewModel using binding

<MyUserControl This="{Binding InjectedView, Mode=OneWayToSource}"/>

Try it out! I've used this pattern many times and you get an interface to the view injected once on startup. This means you maintain separation (Viewmodel can be tested as IView can be mocked), yet you get around the lack of binding support in many third party controls. Plus, its fast. Did you know binding uses reflection?

Finally, I have implemented a chart control that uses this pattern to support MVVM over at ABT Software Services while maintaining a high-performance programmatic API. the chart component is called SciChart, which uses immediate mode rendering and multiple drawing optimisations to produce very high performance charting for scientific / financial applications.

There's a demo project showcasing this pattern on the blog link above. I'd advocate trying out the Attached Property implementation of MiVVM if you are using a third party control.

Upvotes: 0

user572559
user572559

Reputation:

I personally think that the messaging is a bit too excessive for what you're trying to achieve, matter of taste though. Can you not use Adapter or attached behavior patterns? That's what they typically use to substitute for the missing functionality. If you can instanciate your chart in Xaml (which I hope you do), I'd recommend to use attached behaviors, otherwise use and apater (for the elements which have no public constructors or any other tricky stuff) and instanciate it in the code.

For any class, which supports imperative calls only you can allways come up with a compensating behavior. here's a quick sample:

Code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApplication1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        public Dictionary<int, int> MyValues
        {
            get
            {
                return Enumerable.Range(1, 3).ToDictionary(k => k, v => v);
            }
        }
    }

    // component with the 'missing' property
    public class Imperative : FrameworkElement
    {
        public void Add(int x, int y)
        {
            MessageBox.Show(string.Format("{0}_{1}", x, y));
        }
    }

    // compensating behavior
    public class DeclarativeBehavior : DependencyObject
    {
        public static DependencyProperty MissingPropertyProperty =
            DependencyProperty.RegisterAttached("MissingProperty",
            typeof(Dictionary<int, int>),
            typeof(DeclarativeBehavior),
            new PropertyMetadata((o, e) => 
            { 
                //
                Imperative imperative = (Imperative)o;
                Dictionary<int, int> values = (Dictionary<int, int>)e.NewValue;


                if (imperative != null)
                {
                    foreach (KeyValuePair<int, int> value in values)
                    {
                        imperative.Add(value.Key, value.Value);
                    }
                }
            }));

        public static void SetMissingProperty(DependencyObject o, Dictionary<int, int> e)
        {
            o.SetValue(DeclarativeBehavior.MissingPropertyProperty, e);
        }

        public static Dictionary<int, int> GetMissingProperty(DependencyObject o)
        {
            return (Dictionary<int, int>)o.GetValue(DeclarativeBehavior.MissingPropertyProperty);
        }
    }
}

XAML

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <!--black box, which supports imperative calls is extended to support declarative calls too-->
        <local:Imperative local:DeclarativeBehavior.MissingProperty="{Binding MyValues, 
            RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
    </Grid>
</Window>

Upvotes: 1

Related Questions