Set focus on another control when a key pressed without code-behind - wpf

I'm implementing something like an autosuggestion control: I have a user control that contains a TextBox and a ListBox. When the user enters text I'm handing it with System.Windows.Interactivity behaviors and filling the ListBox with some values...
Everything works fine... but I want to enable the user to select items in the ListBox (i.e. to set Focus on the ListBox) when the down arrow key is pressed.
I know that it is possible to handle the KeyPressDown event of the TextBox in the code-behind .cs file but how can I avoid this?

If you already use Interactivity that should not be much of an issue, just implement your own TriggerAction that has the properties Key & TargetName to indentify when and what to focus. Set it in an EventTrigger for PreviewKeyDown.
Sample implementation & use:
<TextBox>
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewKeyDown">
<t:KeyDownFocusAction Key="Down"
Target="{Binding ElementName=lbx}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
<ListBox Name="lbx" ItemsSource="{Binding Data}" />
class KeyDownFocusAction : TriggerAction<UIElement>
{
public static readonly DependencyProperty KeyProperty =
DependencyProperty.Register("Key", typeof(Key), typeof(KeyDownFocusAction));
public Key Key
{
get { return (Key)GetValue(KeyProperty); }
set { SetValue(KeyProperty, value); }
}
public static readonly DependencyProperty TargetProperty =
DependencyProperty.Register("Target", typeof(UIElement), typeof(KeyDownFocusAction), new UIPropertyMetadata(null));
public UIElement Target
{
get { return (UIElement)GetValue(TargetProperty); }
set { SetValue(TargetProperty, value); }
}
protected override void Invoke(object parameter)
{
if (Keyboard.IsKeyDown(Key))
{
Target.Focus();
}
}
}
Tested it and it works, note that KeyDown does not because the arrow keys are intercepted and marked as handled by the TextBox.

I don't think you can avoid it
What's wrong with capturing the KeyDown event of the TextBox and if it's an Up or Down arrow key, just trigger the ListBox.KeyDown event in the code behind?
I see no reason not to use code-behind in MVVM if it is to provide view-specific functionality such as focus

This answer is based on the one from H.B., and adds support for checking to see if the Ctrl key is pressed. This means it can handle keycombinations such as Ctrl-F for find.
XAML
<TextBox>
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewKeyDown">
<t:KeyDownFocusAction Key="Down" Ctrl="True"
Target="{Binding ElementName=lbx}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
<ListBox Name="lbx" ItemsSource="{Binding Data}" />
Namespaces
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
See help on adding System.Windows.Interactivity.
DependencyProperty
public class KeyDownFocusAction : TriggerAction<UIElement>
{
public static readonly DependencyProperty KeyProperty =
DependencyProperty.Register("Key", typeof(Key), typeof(KeyDownFocusAction));
public Key Key
{
get { return (Key)GetValue(KeyProperty); }
set { SetValue(KeyProperty, value); }
}
public static readonly DependencyProperty CtrlProperty =
DependencyProperty.Register("Ctrl", typeof(bool), typeof(KeyDownFocusAction));
public bool Ctrl
{
get { return (bool)GetValue(CtrlProperty); }
set { SetValue(CtrlProperty, value); }
}
public static readonly DependencyProperty TargetProperty =
DependencyProperty.Register("Target", typeof(UIElement), typeof(KeyDownFocusAction), new UIPropertyMetadata(null));
public UIElement Target
{
get { return (UIElement)GetValue(TargetProperty); }
set { SetValue(TargetProperty, value); }
}
protected override void Invoke(object parameter)
{
if (Keyboard.IsKeyDown(Key))
{
if (Ctrl == true)
{
if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
{
Target.Focus();
}
}
}
}
}

Related

Handling Key press & release events in MVVM

I am developing a wpf application using MVVM pattern. I need to separately handle Key press & release events, (e.g. in media players fwd/rev happen till user keeps key pressed & stops when he releases).
After searching a lot, still I couldn't find any way to do it. Can anybody please help.
Thanks for your suggestions.
I found a way to do this by using interactivity triggers & dependency property.
Following is the dependency property for Command.
public class EventToCommand : TriggerAction<DependencyObject>
{
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
// Using a DependencyProperty as the backing store for Command. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(EventToCommand), new PropertyMetadata(null));
protected override void Invoke(object parameter)
{
if (Command != null
&& Command.CanExecute(parameter))
{
Command.Execute(parameter);
}
}
}
Then just use it in the xaml as below:
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyUp">
<ap:EventToCommand Command="{Binding KeyReleaseCommand}"></ap:EventToCommand>
</i:EventTrigger>
<i:EventTrigger EventName="KeyDown">
<ap:EventToCommand Command="{Binding KeyDownCommand}"></ap:EventToCommand>
</i:EventTrigger>
</i:Interaction.Triggers>
Where the KeyReleaseCommand & KeyDownCommand are RelayCommand in your ViewModel.
public MainViewModel()
{
KeyDownCommand = new RelayCommand<KeyEventArgs>(OnKeyDown, null);
KeyReleaseCommand = new RelayCommand<KeyEventArgs>(OnKeyRelease, null);
}
private void OnKeyRelease(KeyEventArgs args)
{
if (args.KeyboardDevice.Modifiers == ModifierKeys.Alt)
{
if (args.SystemKey == Key.Left)
{
Trace.WriteLine("ALT+LEFT Released");
}
}
}
public void OnKeyDown(KeyEventArgs args)
{
if (args.IsRepeat)
return;
if (args.KeyboardDevice.Modifiers == ModifierKeys.Alt)
{
if(args.SystemKey == Key.Left)
{
Trace.WriteLine("ALT+LEFT");
}
}
}
I'm guessing, You will bind the Command to Button. If you want the Commands to fire repeatedly, you can use RepeatButton. It was designed for that purpose.You can bind your command to Command Property. It will fire your methods repeatedly until RepeatButton is released.

Silverlight TextBox VisibilityChanged event

I'm working on a Windows Phone app using Silverlight C# and XAML. My page contains a ListBox which renders a list of databound objects that the user can manipulate, i.e. add/rename/delete.
I've got it working that the add/rename of items is done in-place, i.e. by swapping a TextBlock for a TextBox depending on the state of the object (bool IsEditable property) and making use of a parameterized VisibilityConverter to manage the opposite Visibility states.
<UserControl.Resources>
<local:VisibilityConverter x:Key="VisibilityConverter" True="Visible" False="Collapsed"/>
<local:VisibilityConverter x:Key="InvertedVisibility" True="Collapsed" False="Visible"/>
</UserControl.Resources>
...
<TextBlock Text="{Binding Name}" Visibility="{Binding IsEditable, Converter={StaticResource InvertedVisibility}}" />
<TextBox Text="{Binding Name}" Visibility="{Binding IsEditable, Converter={StaticResource VisibilityConverter}}"/>
The thing is that I also want the TextBox to automatically grab focus when it becomes visible, so that the on-screen keyboard pops up without the user having to tap the TextBox.
Since there's no VisibilityChanged event on a regular TextBox, I subclassed TextBox to TextBox2 and added my own:
public class TextBox2 : TextBox
{
public TextBox2()
{
DefaultStyleKey = typeof(TextBox);
}
public static readonly DependencyProperty VisibilityChangedProperty = DependencyProperty.Register(
"VisibilityChanged",
typeof(string),
typeof(TextBox2),
new PropertyMetadata("Set the VisibilityChanged event handler"));
public event VisibilityChangedEventHandler VisibilityChanged;
public delegate void VisibilityChangedEventHandler(object sender, EventArgs e);
public new Visibility Visibility
{
get
{
return base.Visibility;
}
set
{
if (base.Visibility != value)
{
base.Visibility = value;
VisibilityChanged(this, new EventArgs());
}
}
}
}
Now my XAML looks like this:
<TextBlock Text="{Binding Name}" Visibility="{Binding IsEditable, Converter={StaticResource InvertedVisibility}}"/>
<local:TextBox2 Text="{Binding Name}" Visibility="{Binding IsEditable, Converter={StaticResource VisibilityConverter}}" VisibilityChanged="ListEdit_VisibilityChanged"/>
And the event handler like this:
void ListEdit_VisibilityChanged(object sender, EventArgs e)
{
TextBox textBox = (TextBox)sender;
if (textBox.Visibility == System.Windows.Visibility.Collapsed)
return;
textBox.Focus();
}
The TextBox2 renders properly and behaves just like a TextBox at runtime, but my VisibilityChanged event handler is not firing when the databinding flips the value of IsEditable.
IsEditable defines the Visibility and the TextBox2 does become visible correctly, so the databinding is working.
I can cause the event to fire programmatically by getting hold of the TextBox2 instance and setting the Visibility of that in code. That also works.
But this databinding scenario being responsible for setting the Visibility seems not to work.
Any ideas why not?
Here are 2 solutions that I use.
Solution 1 needs no sub class, but solution 2 is more reusable.
1. You can subscribe to the Loaded event of the TextBox, and force a focus, like so:
void TextBox_Loaded_Focus(object sender, System.Windows.RoutedEventArgs e) {
ForceFocusControl((Control)sender);
}
void ForceFocusControl(Control control) {
control.Focus();
if (FocusManager.GetFocusedElement() != control) {
Dispatcher.BeginInvoke(() => ForceFocusControl(control));
}
}
This solution goes into a recursive loop though, you might want to add some checks to make it safer.
2. Keep your subclass TextBox2, and rather create a private MyVisibility dependency property that you bind to the Visibility property of the base class, but also specify a DependencyProperty_Changed handler, like so:
/// <summary>
/// <see cref="TextBox2"/> will focus itself when it becomes visible.
/// </summary>
public sealed class TextBox2 : TextBox {
public TextBox2() {
SetBinding(TextBox2.MyVisibilityProperty, new Binding("Visibility") { Source = this });
}
static readonly DependencyProperty MyVisibilityProperty = DependencyProperty.Register(
/* name = */ "MyVisibilityProperty",
/* property type = */ typeof(Visibility),
/* owner type = */ typeof(TextBox2),
/* meta = */ new PropertyMetadata(MyVisibilityProperty_Changed));
static void MyVisibilityProperty_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e) {
TextBox2 TextBox2 = (TextBox2)d;
if (TextBox2.Visibility == Visibility.Visible) {
TextBox2.Focus();
}
}
}
This is how my TextBox2 class looks now:
public class TextBox2 : TextBox
{
public event VisibilityChangedEventHandler VisibilityChanged;
public delegate void VisibilityChangedEventHandler(object sender, EventArgs e);
public static readonly DependencyProperty VisibilityChangedProperty = DependencyProperty.Register(
"VisibilityChanged", typeof(VisibilityChangedEventHandler), typeof(TextBox2), null);
static readonly DependencyProperty MirrorVisibilityProperty = DependencyProperty.Register(
"MirrorVisibility", typeof(Visibility), typeof(TextBox2), new PropertyMetadata(MirrorVisibilityChanged));
public TextBox2()
{
SetBinding(TextBox2.MirrorVisibilityProperty, new Binding("Visibility") { Source = this });
}
static void MirrorVisibilityChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
((TextBox2)obj).VisibilityChanged(obj, null); // raise event
}
}

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

Executing viewmodels command on enter in TextBox

I want to execute a command in my viewmodel when the user presses enter in a TextBox.
The command works when bound to a button.
<Button Content="Add" Command="{Binding Path=AddCommand}" />
But I can't bring it to work from the TextBox.
I tried an Inputbinding, but it didn't work.
<TextBox.InputBindings>
<KeyBinding Command="{Binding Path=AddCommand}" Key="Enter"/>
</TextBox.InputBindings>
I also tried to set the working button as default, but it doesn't get executed when enter is pressed.
Thanks for your help.
I know I am late to the party, but I got this to work for me. Try using Key="Return" instead of Key="Enter"
Here is the full example
<TextBox Text="{Binding FieldThatIAmBindingToo, UpdateSourceTrigger=PropertyChanged}">
<TextBox.InputBindings>
<KeyBinding Command="{Binding AddCommand}" Key="Return" />
</TextBox.InputBindings>
</TextBox>
Make sure to use UpdateSourceTrigger=PropertyChanged in your binding, otherwise the property will not be updated until focus is lost, and pressing enter will not lose focus...
Hope this was helpful!
You have probably not made the command a property, but a field. It only works to bind to properties. Change your AddCommand to a property and it will work. (Your XAML works fine for me with a property instead of a field for the command -> no need for any code behind!)
Here's an attached dependency property I created for this. It has the advantage of ensuring that your text binding is updated back to the ViewModel before the command fires (useful for silverlight which doesn't support the property changed update source trigger).
public static class EnterKeyHelpers
{
public static ICommand GetEnterKeyCommand(DependencyObject target)
{
return (ICommand)target.GetValue(EnterKeyCommandProperty);
}
public static void SetEnterKeyCommand(DependencyObject target, ICommand value)
{
target.SetValue(EnterKeyCommandProperty, value);
}
public static readonly DependencyProperty EnterKeyCommandProperty =
DependencyProperty.RegisterAttached(
"EnterKeyCommand",
typeof(ICommand),
typeof(EnterKeyHelpers),
new PropertyMetadata(null, OnEnterKeyCommandChanged));
static void OnEnterKeyCommandChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
ICommand command = (ICommand)e.NewValue;
FrameworkElement fe = (FrameworkElement)target;
Control control = (Control)target;
control.KeyDown += (s, args) =>
{
if (args.Key == Key.Enter)
{
// make sure the textbox binding updates its source first
BindingExpression b = control.GetBindingExpression(TextBox.TextProperty);
if (b != null)
{
b.UpdateSource();
}
command.Execute(null);
}
};
}
}
You use it like this:
<TextBox
Text="{Binding Answer, Mode=TwoWay}"
my:EnterKeyHelpers.EnterKeyCommand="{Binding SubmitAnswerCommand}"/>
You need to define Gesture instead of Key property of the KeyBinding:
<TextBox.InputBindings>
<KeyBinding Gesture="Enter" Command="{Binding AddCommand}"/>
</TextBox.InputBindings>
In addition to Mark Heath's answer, I took the class one step further by implementing Command Parameter attached property in this way;
public static class EnterKeyHelpers
{
public static ICommand GetEnterKeyCommand(DependencyObject target)
{
return (ICommand)target.GetValue(EnterKeyCommandProperty);
}
public static void SetEnterKeyCommand(DependencyObject target, ICommand value)
{
target.SetValue(EnterKeyCommandProperty, value);
}
public static readonly DependencyProperty EnterKeyCommandProperty =
DependencyProperty.RegisterAttached(
"EnterKeyCommand",
typeof(ICommand),
typeof(EnterKeyHelpers),
new PropertyMetadata(null, OnEnterKeyCommandChanged));
public static object GetEnterKeyCommandParam(DependencyObject target)
{
return (object)target.GetValue(EnterKeyCommandParamProperty);
}
public static void SetEnterKeyCommandParam(DependencyObject target, object value)
{
target.SetValue(EnterKeyCommandParamProperty, value);
}
public static readonly DependencyProperty EnterKeyCommandParamProperty =
DependencyProperty.RegisterAttached(
"EnterKeyCommandParam",
typeof(object),
typeof(EnterKeyHelpers),
new PropertyMetadata(null));
static void OnEnterKeyCommandChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
ICommand command = (ICommand)e.NewValue;
Control control = (Control)target;
control.KeyDown += (s, args) =>
{
if (args.Key == Key.Enter)
{
// make sure the textbox binding updates its source first
BindingExpression b = control.GetBindingExpression(TextBox.TextProperty);
if (b != null)
{
b.UpdateSource();
}
object commandParameter = GetEnterKeyCommandParam(target);
command.Execute(commandParameter);
}
};
}
}
Usage:
<TextBox Text="{Binding Answer, Mode=TwoWay}"
my:EnterKeyHelpers.EnterKeyCommand="{Binding SubmitAnswerCommand}"
my:EnterKeyHelpers.EnterKeyCommandParam="your parameter"/>

WPF MVVM Chart change axes

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

Resources