Pre loader

Template items of an AxisCollection possible

Welcome to the SciChart Forums!

  • Please read our Question Asking Guidelines for how to format a good question
  • Some reputation is required to post answers. Get up-voted to avoid the spam filter!
  • We welcome community answers and upvotes. Every Q&A improves SciChart for everyone

WPF Forums | JavaScript Forums | Android Forums | iOS Forums

Answered
1
0

I would like to template items of an AxisCollection assigned to a sciChartSurface in binding of an AxisItemCollectionViewModel. But it looks like I cannot do it as Visual Studio won’t compile my tries.

What i am looking for is something like the EEG 16 channel sample. There the ListBoxItems are styled via a template which is bound to a ViewModelItem. The ListBox itsself is bound to a ViewModelCollection. Depending on how many entries there are entries in the listbox.

So I would like to do something like this:

<SciChartSurface.AxisCollection ItemsSource="{Binding MyAxisCollectionViewModels}" ItemTemplate="{StaticResource AxisItemTemplate}" />

Is this possible? I don’t know how many y-axes i will have. They are configurable by the user.

  • You must to post comments
Best Answer
1
0

Since this is an FAQ, I have created a sample how to achieve true MVVM binding of AxisViewModel to Axis in SciChart.

The trick is to use an attached behaviour, which we will create below.

1. Define your AxisViewModel

In this AxisViewModel we define VisibleRange, AxisId, Title and AutoRange. Theoretically you can define anything you wish to control from the ViewModel.

public class AxisViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private IRange _visibleRange;
    private string _title;
    private AutoRange _autoRange;
    private string _axisId;

    public IRange VisibleRange
    {
        get { return _visibleRange; }
        set
        {
            if (_visibleRange != value)
            {
                _visibleRange = value;
                OnPropertyChanged("VisibleRange");
            }                
        }
    }

    public string AxisId
    {
        get { return _axisId; }
        set
        {
            if (_axisId != value)
            {
                _axisId = value;
                OnPropertyChanged("AxisId");
            }
        }
    }

    public string Title
    {
        get { return _title; }
        set
        {
            if (_title != value)
            {
                _title = value;
                OnPropertyChanged("Title");
            }
        }
    }

    public AutoRange AutoRange
    {
        get { return _autoRange; }
        set
        {
            if (_autoRange != value)
            {
                _autoRange = value;
                OnPropertyChanged("AutoRange");
            }
        }
    }

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

2. Define an Attached Behaviour to create and bind Axes

Below, an attached behaviour manages synchronization of Axis instances with AxisViewModel instances. As AxisViewModels are added or removed, new Axes instances are created and bound to the ViewModels:

public class YAxesCollectionBehaviour
{
    /// <summary>
    /// Defines the AxisSource dependency property, which binds to an ObservableCollection of AxisViewModel and updates the SciChartSurface YAxes collection as the viewmodel collection changes
    /// </summary>
    public static readonly DependencyProperty AxisSourceProperty = DependencyProperty.RegisterAttached(
        "AxisSource", typeof(ObservableCollection<AxisViewModel>), typeof(YAxesCollectionBehaviour), new PropertyMetadata(default(ObservableCollection<AxisViewModel>), OnAxisSourceChanged));        

    public static void SetAxisSource(DependencyObject element, ObservableCollection<AxisViewModel> value)
    {
        element.SetValue(AxisSourceProperty, value);
    }

    public static ObservableCollection<AxisViewModel> GetAxisSource(DependencyObject element)
    {
        return (ObservableCollection<AxisViewModel>)element.GetValue(AxisSourceProperty);
    }

    /// <summary>
    /// Defines the AxisStyle dependency property, which is a global style to apply to all generated Axis
    /// </summary>
    public static readonly DependencyProperty AxisStyleProperty = DependencyProperty.RegisterAttached(
        "AxisStyle", typeof (Style), typeof (YAxesCollectionBehaviour), new PropertyMetadata(default(Style), OnAxisStyleChanged));

    public static void SetAxisStyle(DependencyObject element, Style value)
    {
        element.SetValue(AxisStyleProperty, value);
    }

    public static Style GetAxisStyle(DependencyObject element)
    {
        return (Style) element.GetValue(AxisStyleProperty);
    }

    private static void OnAxisStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var scs = d as SciChartSurface;
        if (scs == null) return;

        var axisStyle = e.NewValue as Style;

        // On AxisStyle changed, apply the new style to all existing Axis in the collection
        foreach (var axis in scs.YAxes.Cast<AxisBase>())
        {
            axis.Style = axisStyle;
        }
    }

    // When the ObservableCollection of AxisViewModels is changed (new instance), subscribe to collection changed events
    private static void OnAxisSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var scs = d as SciChartSurface;
        if (scs == null) return;

        var newCollection = e.NewValue as ObservableCollection<AxisViewModel>;
        var axisStyle = YAxesCollectionBehaviour.GetAxisStyle(scs);

        if (newCollection != null)
        {
            // Subscribe to ObservableCollection<AxisViewModel> collection changes to synchronize axes 
            newCollection.CollectionChanged += (s, arg) => OnAxisViewModelCollectionChanged(newCollection, scs.YAxes, arg, axisStyle);

            // Force rebuilding of axes on the destination
            OnAxisViewModelCollectionChanged(newCollection, scs.YAxes, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset), axisStyle);
        }

        // TODO: Exercise for the reader. Handle collection swapped out when e.OldValue != null and unsubscribe collection changed events
    }

    // When the ObservableCollection of AxisViewModels changes, create and bind new Axis instances
    private static void OnAxisViewModelCollectionChanged(ObservableCollection<AxisViewModel> source, AxisCollection destination, NotifyCollectionChangedEventArgs e, Style axisStyle)
    {
        if (e.Action == NotifyCollectionChangedAction.Reset)
        {
            // Complete reset of ObservableCollection. Rebuild the axis collection
            destination.Clear();
            foreach (var axisViewModel in source)
            {
                destination.Add(CreateAndBindAxis(axisViewModel, axisStyle));
            }
        }

        if (e.OldItems != null)
        {
            // Items removed, remove the corresponding axes
            var avmList = e.OldItems.Cast<AxisViewModel>().ToList();
            for (int i = destination.Count - 1; i >= 0; i--)
            {
                if (avmList.Contains(((AxisBase)destination[i]).DataContext))
                {
                    ((AxisBase)destination[i]).DataContext = null;
                    destination.RemoveAt(i);
                }
            }   
        }
        if (e.NewItems != null)
        {
            // Items added, add new Axis
            foreach (var axisViewModel in e.NewItems.Cast<AxisViewModel>())
            {
                destination.Add(CreateAndBindAxis(axisViewModel, axisStyle));
            }             
        }
    }

    private static IAxis CreateAndBindAxis(AxisViewModel axisViewModel, Style axisStyle)
    {
        // Creates an Axis, applies the global style, and sets the DataContext
        var axis = new NumericAxis();
        axis.Style = axisStyle;       
        axis.DataContext = axisViewModel;                   
        return axis;
    }
}

3. Apply the Attached property in XAML

The final step is to apply the attached property in XAML. Here is our Window code which does this. Also note that we apply a global AxisStyle which contains the bindings between generated Axis and AxisViewModel.

<Window x:Class="WpfApplication15.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:s="http://schemas.abtsoftware.co.uk/scichart"
        xmlns:local="clr-namespace:WpfApplication15"
        Title="MainWindow" Height="350" Width="525">

    <Window.Resources>
        <Style x:Key="AxisStyle" TargetType="s:AxisBase">
            <Setter Property="VisibleRange" Value="{Binding VisibleRange, Mode=TwoWay}"/>
            <Setter Property="AutoRange" Value="{Binding AutoRange, Mode=TwoWay}"/>
            <Setter Property="AxisTitle" Value="{Binding Title, Mode=TwoWay}"/>
            <Setter Property="Id" Value="{Binding AxisId, Mode=TwoWay}"/>
            <!-- todo. more bindings here as you expand AxisViewModel -->
        </Style>

        <local:MainViewModel x:Key="mvm"/>
    </Window.Resources>

    <Grid DataContext="{StaticResource mvm}">
        <Grid.RowDefinitions>
            <RowDefinition Height="32"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <StackPanel Orientation="Horizontal">
            <Button Content="Add Axis" Command="{Binding AddAxisCommand}"/>
            <Button Content="Remove Axis" Command="{Binding RemoveAxisCommand}"/>
        </StackPanel>

        <s:SciChartSurface local:YAxesCollectionBehaviour.AxisSource="{Binding YAxesViewModels}"
                           local:YAxesCollectionBehaviour.AxisStyle="{StaticResource AxisStyle}"
                           Grid.Row="1">
            <s:SciChartSurface.XAxis>
                <s:NumericAxis/>
            </s:SciChartSurface.XAxis>
        </s:SciChartSurface>
    </Grid>
</Window>

Wrapping it all up

We also need a MainViewModel where we will add and remove axis instances. To create a new axis, simply add an AxisViewModel to the MainViewModel. YAxesViewModels collection. To remove one, simply remove the ViewModel. To update the axis, simply change properties on the AxisViewModel instance.

Demonstration

See attached for the demo solution, which compiles with SciChart v3.4 and Visual Studio 2013.

enter image description here

Attachments
  • Uwe Hafner
    excellent solution.
  • HARISHTEI
    The URL does not exist. Could you please host the example once more
  • Andrew Burnett-Thompson
    Hi Harishtei, forum attachment issue is now fixed!!
  • You must to post comments
Showing 1 result
Your Answer

Please first to submit.

Try SciChart Today

Start a trial and discover why we are the choice
of demanding developers worldwide

Start TrialCase Studies