Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF MVVM Chart change axes

Tags:

mvvm

binding

wpf

I'm new to WPF and MVVM. I'm struggling to determine the best way to change the view of a chart. That is, initially a chart might have the axes: X - ID, Y - Length, and then after the user changes the view (either via lisbox, radiobutton, etc) the chart would display the information: X - Length, Y - ID, and after a third change by the user it might display new content: X - ID, Y - Quality.

My initial thought was that the best way to do this would be to change the bindings themselves. But I don't know how tell a control in XAML to bind using a Binding object in the ViewModel, or whether it's safe to change that binding in runtime?

Then I thought maybe I could just have a generic Model that has members X and Y and populate them as needed in the viewmodel?

My last thought was that I could have 3 different chart controls and just hide and show them as appropriate.

What is the CORRECT/SUGGESTED way to do this in the MVVM pattern? Any code examples would be greatly appreciated.

Thanks

Here's what I have for the bind to bindings method:

XAML:

        <charting:Chart.Series>
            <charting:BubbleSeries Name="bubbleSeries1"
                                   ClipToBounds="False"
                                   model:MakeDependencyProperty.IndependentValueBinding="{Binding AxisChoice.XBinding}"
                                   model:MakeDependencyProperty.DependentValueBinding="{Binding AxisChoice.YBinding}"
                                   model:MakeDependencyProperty.SizeValueBinding="{Binding AxisChoice.SizeBinding}"
                                   IsSelectionEnabled="True" SelectionChanged="bubbleSeries1_SelectionChanged"
                                   ItemsSource="{Binding Data}">
            </charting:BubbleSeries>
        </charting:Chart.Series>

        <ComboBox Height="100" Name="listBox1" Width="120" SelectedItem="{Binding AxisChoice}">
            <model:AxisGroup XBinding="{Binding Performance}" YBinding="{Binding TotalCount}" SizeBinding="{Binding TotalCount}" Selector.IsSelected="True"/>
            <model:AxisGroup XBinding="{Binding ID}" YBinding="{Binding TotalCount}" SizeBinding="{Binding BadPerformance}"/>
            <model:AxisGroup XBinding="{Binding ID}" YBinding="{Binding BadPerformance}" SizeBinding="{Binding TotalCount}"/>
        </ComboBox>

AxisGroup:

public class AxisGroup : DependencyObject// : FrameworkElement
{
    public Binding XBinding { get; set; }
    public Binding YBinding { get; set; }
    public Binding SizeBinding { get; set; }
}

DP:

public class MakeDependencyProperty : DependencyObject
{
    public static Binding GetIndependentValueBinding(DependencyObject obj) { return (Binding)obj.GetValue(IndependentValueBindingProperty); }
    public static void SetIndependentValueBinding(DependencyObject obj, Binding value) { obj.SetValue(IndependentValueBindingProperty, value); }
    public static readonly DependencyProperty IndependentValueBindingProperty =
        DependencyProperty.RegisterAttached("IndependentValueBinding", typeof(Binding), typeof(MakeDependencyProperty), new PropertyMetadata { PropertyChangedCallback = (obj, e) => { ((BubbleSeries)obj).IndependentValueBinding = (Binding)e.NewValue;}});


    public static Binding GetDependentValueBinding(DependencyObject obj) { return (Binding)obj.GetValue(DependentValueBindingProperty); }
    public static void SetDependentValueBinding(DependencyObject obj, Binding value) { obj.SetValue(DependentValueBindingProperty, value); }
    public static readonly DependencyProperty DependentValueBindingProperty =
        DependencyProperty.RegisterAttached("DependentValueBinding", typeof(Binding), typeof(MakeDependencyProperty), new PropertyMetadata { PropertyChangedCallback = (obj, e) => { ((BubbleSeries)obj).DependentValueBinding = (Binding)e.NewValue; } });

    public static Binding GetSizeValueBinding(DependencyObject obj) { return (Binding)obj.GetValue(SizeValueBindingProperty); }
    public static void SetSizeValueBinding(DependencyObject obj, Binding value) { obj.SetValue(SizeValueBindingProperty, value); }
    public static readonly DependencyProperty SizeValueBindingProperty =
        DependencyProperty.RegisterAttached("SizeValueBinding", typeof(Binding), typeof(MakeDependencyProperty), new PropertyMetadata { PropertyChangedCallback = (obj, e) => { ((BubbleSeries)obj).SizeValueBinding = (Binding)e.NewValue; } }); 
}

ViewModel:

public class BubbleViewModel : BindableObject
{
    private IEnumerable<SessionPerformanceInfo> data;
    public IEnumerable<SessionPerformanceInfo> Data { ... }

    public AxisGroup AxisChoice;
}

This generates the following exception: + $exception {"Value cannot be null.\r\nParameter name: binding"} System.Exception {System.ArgumentNullException}

Has something to do with the 4 bindings in teh bubbleSeries. I'm more than likely doing something wrong with binding paths but as I said I'm new to binding and wpf, so any tips would be greatly appreciated.

like image 589
c0uchm0nster Avatar asked Dec 03 '25 09:12

c0uchm0nster


1 Answers

Your initial thought was correct: You can bind to bindings, for example if you want to change both axes together you might have a ComboBox like this:

<ComboBox SelectedItem="{Binding AxisChoice}">
  <my:AxisChoice XBinding="{Binding ID}" YBinding="{Binding Length}" />
  <my:AxisChoice XBinding="{Binding Length}" YBinding="{Binding ID}" />
  <my:AxisChoice XBinding="{Binding ID}" YBinding="{Binding Quality}" />
</ComboBox>

To make this work you need to declare XBinding and YBinding as CLR properties of type "Binding":

public class AxisChoice
{
  public Binding XBinding { get; set; }
  public Binding YBinding { get; set; }
}

Ideally you could then simply bind the DependentValueBinding or IndependentValueBinding of your chart:

<Chart ...>
  <LineSeries
    DependentValueBinding="{Binding AxisChoice.XBinding}"
    IndependentValueBinding="{Binding AxisChoice.YBinding}" />
</Chart>

Unfortunately this does not work because DependentValueBinding and IndependentValueBinding aren't DependencyProperties.

The workaround is to create an attached DependencyProperty to mirror each property that is not a DependencyProperty, for example:

public class MakeDP : DependencyObject
{
  public static Binding GetIndependentValueBinding(DependencyObject obj) { return (Binding)obj.GetValue(IndependentValueBindingProperty); }
  public static void SetIndependentValueBinding(DependencyObject obj, Binding value) { obj.SetValue(IndependentValueBindingProperty, value); }
  public static readonly DependencyProperty IndependentValueBindingProperty = DependencyProperty.RegisterAttached("IndependentValueBinding", typeof(Binding), typeof(MakeDP), new PropertyMetadata
  {
    PropertyChangedCallback = (obj, e) =>
    {
      ((DataPointSeries)obj).IndependentValueBinding = (Binding)e.NewValue;
    }
  });

  public static Binding GetDependentValueBinding(DependencyObject obj) { return (Binding)obj.GetValue(DependentValueBindingProperty); }
  public static void SetDependentValueBinding(DependencyObject obj, Binding value) { obj.SetValue(DependentValueBindingProperty, value); }
  public static readonly DependencyProperty DependentValueBindingProperty = DependencyProperty.RegisterAttached("DependentValueBinding", typeof(Binding), typeof(MakeDP), new PropertyMetadata
  {
    PropertyChangedCallback = (obj, e) =>
      {
        ((DataPointSeries)obj).DependentValueBinding = (Binding)e.NewValue;
      }
  });

}

So your XAML becomes:

<Chart ...>
  <LineSeries
    my:MakeDP.DependentValueBinding="{Binding AxisChoice.XBinding}"
    my:MakeDP.IndependentValueBinding="{Binding AxisChoice,YBinding}" />
</Chart>

If instead you want to change axes separately (two separate ComboBoxes or ListBoxes), you don't need AxisChoice: Simply make the Items or ItemsSource of each ComboBox consist of bindings, and put the a "XBinding" and "YBinding" properties directly in your view model.

Note that if your control exposes a regular property instead of a property of type Binding you can still use this method, but in this case you will use BindingOperations.SetBinding instead of just storing the binding value.

For example, if you want to change the binding of the text in a TextBlock from:

<TextBlock Text="{Binding FirstName}" />

to

<TextBlock Text="{Binding LastName}" />

based on a ComboBox or ListBox selection, you can use an attached property as follows:

<TextBlock my:BindingHelper.TextBinding="{Binding XBinding}" />

The attached property implementation is trivial:

public class BindingHelper : DependencyObject
{
  public static BindingBase GetTextBinding(DependencyObject obj) { return (BindingBase)obj.GetValue(TextBindingProperty); }
  public static void SetTextBinding(DependencyObject obj, BindingBase value) { obj.SetValue(TextBindingProperty, value); }
  public static readonly DependencyProperty TextBindingProperty = DependencyProperty.RegisterAttached("TextBinding", typeof(BindingBase), typeof(BindingHelper), new PropertyMetadata
  {
    PropertyChangedCallback = (obj, e) =>
      BindingOperations.SetBinding(obj, TextBlock.TextProperty, (BindingBase)e.NewValue)
  });
}
like image 189
Ray Burns Avatar answered Dec 05 '25 01:12

Ray Burns



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!