WPF MVVM Chart change axes - 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.

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)
});
}

I'm trying to simplify things so I made the ItemsSource of a ComboBox (Y1-Axis) consist of an observable collection of bindings, and I put the "YBinding" property directly in the ViewModel and set the public binding property as the combobox SelectedItem.
The the dependentvaluebinding is crashing the app though when using the public Binding SelectedY1:
<ComboBox Height="22" Name="comboBox1"
DisplayMemberPath="Source.MetricVarName"
ItemsSource="{Binding AllY1Choices}"
SelectedIndex="0"
SelectedItem="{Binding SelectedY1, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</ComboBox>
<chartingToolkit:LineSeries
ItemsSource="{Binding AllY1Axis}"
IndependentValueBinding="{Binding AccumDate}"
my:MakeDP.DependentValueBinding="{Binding SelectedY1}">
In the VM:
private Binding _Y1axisChoice = new Binding();
private ObservableCollection<Binding> _allY1Choices = new ObservableCollection<Binding>();
public ObservableCollection<Binding> AllY1Choices
{
get { return _allY1Choices; }
set
{
_allY1Choices = value;
OnPropertyChanged("AllY1Choices");
}
}
private Binding _selectedY1 = new Binding();
public Binding SelectedY1
{
get { return _selectedY1; }
set
{
if (_selectedY1 != value)
{
_selectedY1 = value;
OnPropertyChanged("SelectedY1");
}
}
}
In the VM contstructor:
_Y1axisChoice = new Binding("MetricVarID");
_Y1axisChoice.Source = AllY1MetricVars[0];
_selectedY1 = _Y1axisChoice; // set default for combobox
_allY1Choices.Add(_Y1axisChoice);
_Y1axisChoice = new Binding("MetricVarID");
_Y1axisChoice.Source = AllY1MetricVars[1];
_allY1Choices.Add(_Y1axisChoice);
Any thoughts on this? The Binding object "SelectedY1" has Source.MetricID="OldA", and that's a valid value for the dependent value binding.
The error:
An exception of type 'System.InvalidOperationException' occurred in System.Windows.Controls.DataVisualization.Toolkit.dll but was not handled in user code
Additional information: Assigned dependent axis cannot be used. This may be due to an unset Orientation property for the axis or a type mismatch between the values being plotted and those supported by the axis.
Thanks

Related

Cannot update combobox selected index from the view to the viewmodel

Using the MVVM pattern, I've bound the SelectedIndex property of a combo box to a variable in my view model. I can change the combo box selection programmatically from the view model; however, when the user makes a selection from the interface (view), the view model variable is not updated.
Here is the XAML (snipet):
<ComboBox Width="100"
HorizontalContentAlignment="Center"
SelectedIndex="{Binding GroupSortIndex,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
<ComboBoxItem Content="Appearance"/>
<ComboBoxItem Content="Created"/>
<ComboBoxItem Content="Name"/>
</ComboBox>
Here is a portion of the view model:
public int GroupSortIndex
{
get { return (int)GetValue(GroupSortIndexProperty); }
set { SetValue(GroupSortIndexProperty, value); }
}
public static readonly DependencyProperty GroupSortIndexProperty =
DependencyProperty.Register("GroupSortIndex", typeof(int),
typeof(MainWindowViewModel), new UIPropertyMetadata(-1));
What do I need to do to have GroupSortIndex updated when the user makes a selection from the IU?
yes, The SelectedIndex property looks has a bug. i was this problem and i solved it by changing the SelectedIndex to SelectedItem.
Change XAML SelectedIndex to SelectedItem:
<ComboBox ItemsSource="{Binding Path=YourOptionsList}"
SelectedItem="{Binding SelectedOption}" />
Somewhere you must set the DataContext of your Window to reference the collection from your XAML.
and in your ViewModel write this:
public List<String> YourOptionsList { get { return (int)GetValue(YourOptionsListProperty); } set { SetValue(YourOptionsListProperty, value); } }
public static readonly DependencyProperty YourOptionsListProperty = DependencyProperty.Register("YourOptionsList", typeof(List<String>), typeof(MainWindowViewModel), new UIPropertyMetadata(new List<String>()));
public string SelectedOption { get { return (int)GetValue(SelectedOptionProperty); } set { SetValue(SelectedOptionProperty, value); } }
public static readonly DependencyProperty SelectedOptionProperty = DependencyProperty.Register("SelectedOption", typeof(String), typeof(MainWindowViewModel), new UIPropertyMetadata("none");
public MainWindowViewModel()
{
YourOptionsList =new List<String>();
YourOptionsList.Add("Appearance");
YourOptionsList.Add("Created");
YourOptionsList.Add("Name");
}

Silverlight multiple binding

I have a question about binding in silverlight. I made my class for Grid view data column:
public class NavigateItemID : GridViewDataColumn
{
public Binding itemID { get; set; }
public Binding usedType { get; set; }
}
And this is where I implement data column in xaml:
<ext:NavigateItemID UniqueName="objectName"
CellStyle="{Binding Source={StaticResource GridViewCellAlignmentToTop}}"
Width="0.3*"
Header="{Binding Source={StaticResource locResources}, Path=LabelsWrapper.lblObjectNameW}"
TextWrapping="Wrap"
IsReadOnly="True"
DataMemberBinding="{Binding objectName}"
itemID="{Binding itemID}"
usedType="{Binding objectType}" />
I wanted to ask how can I get value of itemID?
Used in this way, itemID will always be null.
The Binding class is a contruct which establishes a relationship between a property in the columns DataContext, and a dependency property on the target type.
In this case, assuming everything else is set up correctly, you must change the type of your properties for the Binding to work.
For Example:
public class NavigateItemID : GridViewDataColumn
{
public int itemID
{
get { return (int)GetValue(itemIDProperty); }
set { SetValue(itemIDProperty, value); }
}
public static readonly DependencyProperty itemIDProperty =
DependencyProperty.Register("itemID", typeof(int), typeof(NavigateItemID), new PropertyMetadata(0));
public object usedType
{
get { return (object)GetValue(usedTypeProperty); }
set { SetValue(usedTypeProperty, value); }
}
public static readonly DependencyProperty usedTypeProperty =
DependencyProperty.Register("usedType", typeof(object), typeof(NavigateItemID), new PropertyMetadata(null));
}
Have a read of this for more on Data Binding

Adding a dependency property to a text box

How can I Add a dependency property to a text box and bind the dependency property to a Boolean property in silver light. my Boolean property is in my view model.
ImageSearchIsFocused is the property which allows me to set the focus on a text box.
<TextBox Text="{Binding ImgSearch, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<i:Interaction.Behaviors>
<common:FocusBehavior HasInitialFocus="True" IsFocused="{Binding ImageSearchIsFocused, Mode=TwoWay}" ></common:FocusBehavior>
</i:Interaction.Behaviors>
</TextBox>
ImageIsFocused Property
bool _ImageSearchIsFocused;
public bool ImageSearchIsFocused
{
get { return _ImageSearchIsFocused; }
set
{
_ImageSearchIsFocused = value;
NotifyPropertyChanged("ImageSearchIsFocused");
}
}
If you want to add a dependency property, you're going to have the subclass the TextBox and add the dependency property to your subclass. Then you can bind that to whatever you like:
public class MyTextBox : TextBox
{
public static readonly DependencyProperty MyBooleanValueProperty = DependencyProperty.Register(
"MyBooleanValue", typeof(bool), typeof(MyTextBox),
new PropertyMetadata(new PropertyChangedCallback(MyBooleanValueChanged)));
public bool MyBooleanValue
{
get { return (bool)GetValue(MyBooleanValueProperty); }
set { SetValue(MyBooleanValueProperty, value); }
}
private static void MyBooleanValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var propValue = (bool)e.NewValue;
var control = d as MyTextBox;
// do something useful
}
}

Reference Usercontrol in a DataTemplate from the DataTemplate's datatype class

I have a datatemplate like this:
<DataTemplate DataType="{x:Type mvvm:ComponentViewModel}">
<v:UCComponents></v:UCComponents>
</DataTemplate>
UCComponent is a usercontrol with a public property called ID. ComponentViewModel also has a property called ID. I would like to set UCComponent ID property in the setter of the ViewModel's property. How can I do that?
This is what I've tried:
private string _ID;
public string ID
{
set { ((UCComponents)((DataTemplate)this).LoadContent()).ID = value; }
}
Error: this cannot be converted from ComponentViewModel to DataTemplate. Any help will be appreciated.
I'm not keeping true to MVVM design pattern, which is probably the reason for my frustration, but is there a way to access the UserControl used in the template?
Amanda
Thanks for the help, but it doesn't work. The getter and setter of ID is never called. This is my code:
public static DependencyProperty IDProperty = DependencyProperty.Register("ID", typeof(Guid), typeof(UCComponents));
public Guid ID
{
get
{ return (Guid)GetValue(IDProperty); }
set
{
SetValue(IDProperty, value);
LoadData();
}
}
I can't make the UserControl a DependencyObject. Is that perhaps the problem?
You can add a dependency property on your user control (UCComponents.xaml.cs) as follows
public static DependencyProperty IDProperty = DependencyProperty.Register(
"ID",
typeof(Object),
typeof(BindingTestCtrl));
public string ID
{
get
{
return (string)GetValue(IDProperty);
}
set
{
SetValue(IDProperty, value);
}
}
then you can bind it using
<DataTemplate DataType="{x:Type mvvm:ComponentViewModel}">
<v:UCComponents ID="{Binding ID}" />
</DataTemplate>
Another solution would be to handle the DataContextChanged event on your user control with something like
private ComponentViewModel _data;
private void UserControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
_data = e.NewValue as ComponentViewModel;
this.ID = _data.ID;
}
Thanks Jim,
I got it to work this way: In my UserControl:
public static DependencyProperty IDProperty = DependencyProperty.Register("ID", typeof(Guid), typeof(UCComponents));
public Guid ID
{
get
{ return (Guid)GetValue(IDProperty); }
set
{
SetValue(IDProperty, value);
LoadData();
}
}
private void UserControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
_data = e.NewValue as ComponentViewModel;
this.ID = _data.ID;
}
In my ViewModel (used as the template type):
public ComponentViewModel(Guid id)
{
DisplayName = "Component";
Glyph = new BitmapImage(new Uri("/Remlife;component/Images/Toolbox/Control.png", UriKind.Relative));
ID = id;
}
And in my Resources file:
<DataTemplate DataType="{x:Type mvvm:ComponentViewModel}">
<v:UCComponents ID="{Binding ID}"/>
</DataTemplate>
Somehow I need to still pass the ID to the ViewModel in it's constructor. Why? I'm not sure, but at least it works now. Thanks for your help.

WPF: Nested DependencyProperties

I have an ObservableCollection of "Layouts" and a "SelectedLocation" DependencyProperty on a Window. The SelectedLocation has a property called "Layout", which is an object containing fields like "Name" etc. I'm trying to bind a combobox to the SelectedLayout but it's not working.
The following does not work, I've tried binding to SelectedItem instead to no avail. I believe it may be something to do with the fact that I'm binding to a subProperty of the SelectedLocation DependencyProperty (though this does implement INotifyPropertyChanged.
<ComboBox Grid.Row="2" Grid.Column="0" x:Name="cboLayout" ItemsSource="{Binding Layouts,ElementName=root}" SelectedValue="{Binding SelectedLocation.Layout.LayoutID,ElementName=root}" DisplayMemberPath="{Binding Name}" SelectedValuePath="LayoutID" />
However, the following works (Also bound to the "SelectedLocation" DP:
<TextBox Grid.Row="4" Grid.Column="1" x:Name="txtName" Text="{Binding SelectedLocation.Name,ElementName=root,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
What type property Layouts has? I suppose something like this this: IEnumerable<Layout>.
But you bind selected value to Layout.LayoutID. So you got situation, when combo box contains Layout objects, and you try to select it by Int identifier. Of course binding engine can't find any Int there.
I have no idea about details of your code, so one thing I could propose: try to reduce your binding expression: SelectedItem="{Binding SelectedLocation.Layout,ElementName=root}.
If no success, provide more code to help me understand what's going on.
====UPDATE====
As I've said, you are obviously doing something wrong. But I am not paranormalist and couldn't guess the reason of your fail (without your code). If you don't want to share your code, I decided to provide simple example in order to demonstrate that everything works. Have a look at code shown below and tell me what is different in your application.
Class Layout which exposes property LayoutId:
public class Layout
{
public Layout(string id)
{
this.LayoutId = id;
}
public string LayoutId
{
get;
private set;
}
public override string ToString()
{
return string.Format("layout #{0}", this.LayoutId);
}
}
Class SelectionLocation which has nested property Layout:
public class SelectedLocation : INotifyPropertyChanged
{
private Layout _layout;
public Layout Layout
{
get
{
return this._layout;
}
set
{
this._layout = value;
this.OnPropertyChanged("Layout");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
var safeEvent = this.PropertyChanged;
if (safeEvent != null)
{
safeEvent(this, new PropertyChangedEventArgs(name));
}
}
}
And Window class with dependency properties (actually, in my example StartupView is UserControl, but it doesn't matter):
public partial class StartupView : UserControl
{
public StartupView()
{
InitializeComponent();
this.Layouts = new Layout[] { new Layout("AAA"), new Layout("BBB"), new Layout("CCC") };
this.SelectedLocation = new SelectedLocation();
this.SelectedLocation.Layout = this.Layouts.ElementAt(1);
}
public IEnumerable<Layout> Layouts
{
get
{
return (IEnumerable<Layout>)this.GetValue(StartupView.LayoutsProperty);
}
set
{
this.SetValue(StartupView.LayoutsProperty, value);
}
}
public static readonly DependencyProperty LayoutsProperty =
DependencyProperty.Register("Layouts",
typeof(IEnumerable<Layout>),
typeof(StartupView),
new FrameworkPropertyMetadata(null));
public SelectedLocation SelectedLocation
{
get
{
return (SelectedLocation)this.GetValue(StartupView.SelectedLocationProperty);
}
set
{
this.SetValue(StartupView.SelectedLocationProperty, value);
}
}
public static readonly DependencyProperty SelectedLocationProperty =
DependencyProperty.Register("SelectedLocation",
typeof(SelectedLocation),
typeof(StartupView),
new FrameworkPropertyMetadata(null));
}
XAML of StartupView:
<UserControl x:Class="Test.StartupView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:self="clr-namespace:HandyCopy"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="Root">
<WrapPanel>
<ComboBox ItemsSource="{Binding Path=Layouts,ElementName=Root}"
SelectedItem="{Binding Path=SelectedLocation.Layout, ElementName=Root}"/>
</WrapPanel>
</UserControl>

Resources