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.
- Uwe Hafner asked 9 years ago
- You must login to post comments
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.
- Andrew Burnett-Thompson answered 9 years ago
-
excellent solution.
-
The URL does not exist. Could you please host the example once more
-
Hi Harishtei, forum attachment issue is now fixed!!
- You must login to post comments
Please login first to submit.