Is there an easy way in WPF to bind VisualStates to enum values? Kinda like DataStateBehavior, but for an Enum?
The best way is to just go ahead and implement a Behavior that does just that -
public class EnumStateBehavior : Behavior<FrameworkElement>
{
public object EnumProperty
{
get { return (object)GetValue(EnumPropertyProperty); }
set { SetValue(EnumPropertyProperty, value); }
}
// Using a DependencyProperty as the backing store for EnumProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty EnumPropertyProperty =
DependencyProperty.Register("EnumProperty", typeof(object), typeof(EnumStateBehavior), new UIPropertyMetadata(null, EnumPropertyChanged));
static void EnumPropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == null) return;
EnumStateBehavior eb = sender as EnumStateBehavior;
VisualStateManager.GoToElementState(eb.AssociatedObject, e.NewValue.ToString(), true);
}
}
The usage is extremely simple - use as follows:
<i:Interaction.Behaviors>
<local:EnumStateBehavior EnumProperty="{Binding MyEnumProperty}" />
</i:Interaction.Behaviors>
You can do it in pure xaml by using a DataTrigger per possible enum value with each trigger calling GoToStateAction with a different state. See the example below. For more details take a look at
Enum driving a Visual State change via the ViewModel.
<i:Interaction.Triggers>
<ei:DataTrigger Binding="{Binding ConfirmedAnswerStatus}" Value="Unanswered">
<ei:GoToStateAction StateName="UnansweredState" UseTransitions="False" />
</ei:DataTrigger>
<ei:DataTrigger Binding="{Binding ConfirmedAnswerStatus}" Value="Correct">
<ei:GoToStateAction StateName="CorrectlyAnsweredState" UseTransitions="True" />
</ei:DataTrigger>
<ei:DataTrigger Binding="{Binding ConfirmedAnswerStatus}" Value="Incorrect">
<ei:GoToStateAction StateName="IncorrectlyAnsweredState" UseTransitions="True" />
</ei:DataTrigger>
</i:Interaction.Triggers>
There is a DataStateSwitchBehavior in SL that could be ported to WPF: Anyone have a DataStateSwitchBehavior for WPF4?
the syntax is pretty straightforward:
<is:DataStateSwitchBehavior Binding="{Binding Orientation}">
<is:DataStateSwitchCase Value="Left" State="LeftState"/>
<is:DataStateSwitchCase Value="Right" State="RightState"/>
<is:DataStateSwitchCase Value="Down" State="DownState"/>
<is:DataStateSwitchCase Value="Up" State="UpState"/>
<is:DataStateSwitchCase/>
I was having issues with the above EnumStateBehavior answer.
The PropertyChanged handler will first trigger when the AssociatedObject is null (since the binding has been set up but the Behavior hasn't been attached yet). Also, even when the behavior is first attached, the target elements of the VisualState animation may not yet exist since the behavior may have been attached before other child visual trees.
The solution was to use the Loaded event on the associated object to ensure the binding's initial state is set.
public class EnumStateBehavior : Behavior<FrameworkElement>
{
public static readonly DependencyProperty BindingProperty =
DependencyProperty.Register(nameof(Binding), typeof(object), typeof(EnumStateBehavior), new UIPropertyMetadata(null, BindingPropertyChanged));
public object Binding
{
get { return (object)GetValue(BindingProperty); }
set { SetValue(BindingProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.Loaded += AssociatedObject_Loaded;
}
protected override void OnDetaching()
{
this.AssociatedObject.Loaded -= AssociatedObject_Loaded;
base.OnDetaching();
}
private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
if (Binding != null)
GoToState();
}
private void GoToState()
{
VisualStateManager.GoToElementState(this.AssociatedObject, Binding.ToString(), true);
}
private static void BindingPropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var eb = (EnumStateBehavior)sender;
if (e.NewValue == null || eb.AssociatedObject == null || !eb.AssociatedObject.IsLoaded)
return;
eb.GoToState();
}
}
Related
in my applications all inputs in textboxes have to be done by a inputbox i wrote myself. so whenever a textbox is klicked, the inputbox pops up. it looks like this:
the textbox in xaml:
<TextBox Text="{Binding Level}" Grid.Row="1" Grid.Column="2" Margin="2">
<TextBox.InputBindings>
<MouseBinding Command="{Binding EnterLevel}" MouseAction="LeftClick" />
</TextBox.InputBindings>
</TextBox>
the command in the VM:
private void ExecuteEnterLevel()
{
var dialog = new BFH.InputBox.InputBox("Level", 1, 1, 4);
dialog.ShowDialog();
Level = Convert.ToInt16(dialog.Input);
}
so the result of the inputbox becomes the text of the inputbox. this works fine.
now my question is: can i do that for all of my textboxes that need that functionality without coding an event for every single textbox? i would like to have "myTextbox" which does it automatically.
what i tried so far:
my textbox:
class MyTextbox : TextBox
{
public MyTextbox()
{
this.MouseDown += MyTextbox_MouseDown;
}
private void MyTextbox_MouseDown(object sender, MouseButtonEventArgs mouseButtonEventArgs)
{
TextBox tb = (TextBox)sender;
var dialog = new BFH.InputBox.InputBox("titel", "input");
dialog.ShowDialog();
tb.Text = dialog.Input;
}
}
and in xaml:
<libraries:MyTextbox/>
but MyTextbox_MouseDown is never executet. i put MessageBox.Show("test") in it without any results. i am doing something wrong i guess.
Since it is not possible to define InputBindings in styles, at least in a straight forward way. I offer to solve the issue by using AttachedProperties
lets start by defining a class for the attached property
namespace CSharpWPF
{
class EventHelper
{
public static ICommand GetLeftClick(DependencyObject obj)
{
return (ICommand)obj.GetValue(LeftClickProperty);
}
public static void SetLeftClick(DependencyObject obj, ICommand value)
{
obj.SetValue(LeftClickProperty, value);
}
// Using a DependencyProperty as the backing store for LeftClick. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LeftClickProperty =
DependencyProperty.RegisterAttached("LeftClick", typeof(ICommand), typeof(EventHelper), new PropertyMetadata(null, OnLeftClickChanged));
private static void OnLeftClickChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement elem = d as FrameworkElement;
ICommand command = e.NewValue as ICommand;
if (command != null)
elem.InputBindings.Add(new MouseBinding(command, new MouseGesture(MouseAction.LeftClick)));
}
}
}
this is basically translation of mouse binding in your code done on property changed.
usage
<TextBox l:EventHelper.LeftClick="{Binding MyCommand}" />
if you wish to apply to all TextBoxes then wrap the same in a generic style targeted to TextBox
<Style TargetType="TextBox">
<Setter Property="l:EventHelper.LeftClick"
Value="{Binding MyCommand}" />
</Style>
l: in the above refers to the name space of the above declared class eg xmlns:l="clr-namespace:CSharpWPF"
a full example
<StackPanel xmlns:l="clr-namespace:CSharpWPF">
<StackPanel.Resources>
<Style TargetType="TextBox">
<Setter Property="l:EventHelper.LeftClick"
Value="{Binding MyCommand}" />
</Style>
</StackPanel.Resources>
<TextBox />
<TextBox />
<TextBox />
<TextBox />
</StackPanel>
Attaching input box behavior
class
class InputHelper
{
public static bool GetIsInputBoxEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(IsInputBoxEnabledProperty);
}
public static void SetIsInputBoxEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsInputBoxEnabledProperty, value);
}
// Using a DependencyProperty as the backing store for IsInputBoxEnabled. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsInputBoxEnabledProperty =
DependencyProperty.RegisterAttached("IsInputBoxEnabled", typeof(bool), typeof(InputHelper), new PropertyMetadata(false, OnIsInputBoxEnabled));
private static void OnIsInputBoxEnabled(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBox tb = d as TextBox;
if ((bool)e.NewValue)
tb.PreviewMouseDown += elem_MouseDown;
else
tb.PreviewMouseDown -= elem_MouseDown;
}
static void elem_MouseDown(object sender, MouseButtonEventArgs e)
{
TextBox tb = sender as TextBox;
var dialog = new BFH.InputBox.InputBox(tb.GetValue(InputBoxTitleProperty), tb.GetValue(InputProperty));
dialog.ShowDialog();
tb.Text = dialog.Input;
}
public static string GetInputBoxTitle(DependencyObject obj)
{
return (string)obj.GetValue(InputBoxTitleProperty);
}
public static void SetInputBoxTitle(DependencyObject obj, string value)
{
obj.SetValue(InputBoxTitleProperty, value);
}
// Using a DependencyProperty as the backing store for InputBoxTitle. This enables animation, styling, binding, etc...
public static readonly DependencyProperty InputBoxTitleProperty =
DependencyProperty.RegisterAttached("InputBoxTitle", typeof(string), typeof(InputHelper), new PropertyMetadata(null));
public static string GetInput(DependencyObject obj)
{
return (string)obj.GetValue(InputProperty);
}
public static void SetInput(DependencyObject obj, string value)
{
obj.SetValue(InputProperty, value);
}
// Using a DependencyProperty as the backing store for Input. This enables animation, styling, binding, etc...
public static readonly DependencyProperty InputProperty =
DependencyProperty.RegisterAttached("Input", typeof(string), typeof(InputHelper), new PropertyMetadata(null));
}
usage
<TextBox l:InputHelper.IsInputBoxEnabled="true"
l:InputHelper.InputBoxTitle="Title"
l:InputHelper.Input="Input" />
or via styles
<Style TargetType="TextBox">
<Setter Property="l:InputHelper.IsInputBoxEnabled"
Value="true" />
<Setter Property="l:InputHelper.Title"
Value="true" />
<Setter Property="l:InputHelper.Input"
Value="Input" />
</Style>
using attached properties will enable you to attach this behavior to existing classes instead of inheriting from them.
if you are inheriting the text box class you can add two properties to the same
eg
class MyTextbox : TextBox
{
public MyTextbox()
{
this.PreviewMouseDown += MyTextbox_MouseDown;
}
private void MyTextbox_MouseDown(object sender, MouseButtonEventArgs mouseButtonEventArgs)
{
TextBox tb = (TextBox)sender;
var dialog = new BFH.InputBox.InputBox(InputBoxTitle, Input);
dialog.ShowDialog();
tb.Text = dialog.Input;
}
public string InputBoxTitle { get; set; }
public string Input { get; set; }
}
usage
<libraries:MyTextbox InputBoxTitle="Title"
Input="Input" />
I have bound a ListBox to my ViewModel including the ListBox.SelectedItem. I want to change a visual state depending on if there is one selected or not, but The following doesn't update the state initially, so it stays in the wrong state:
<DataStateBehavior Binding="{Binding SelectedCamera}" Value="{x:Null}" TrueState="CameraSettingsUnselected" FalseState="CameraSettingsSelected"/>
Why is that and how to fix it?
The problem here seems to be that the binding initially evaluates to null and thus doesn't fire the change notification required for the evaluation and state change.
I've fixed it with the following subclass:
public class FixedDataStateBehavior: DataStateBehavior
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += (sender, routedEventArgs) =>
{
var bindingExpression = BindingOperations.GetBindingExpression(this, BindingProperty);
SetCurrentValue(BindingProperty,new object());
bindingExpression.UpdateTarget();
};
}
}
and used it like this:
<FixedDataStateBehavior Binding="{Binding SelectedCamera}" Value="{x:Null}" TrueState="CameraSettingsUnselected" FalseState="CameraSettingsSelected"/>
The answer above works, but I ended up creating a more generic Behavior class that would simply work with all bindings without needing to specify them individually.
public class RefreshDataContextBehavior : Behavior<FrameworkElement>
{
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.Loaded += AssociatedObject_Loaded;
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.Loaded -= AssociatedObject_Loaded;
}
private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
var dc = this.AssociatedObject.DataContext;
this.AssociatedObject.DataContext = null;
this.AssociatedObject.DataContext = dc;
}
}
Then simply insert it into the XAML like so on the object which has the DataContext:
<i:Interaction.Behaviors>
<local:RefreshDataContextBehavior />
</i:Interaction.Behaviors>
I want to invoke a command when ENTER is pressed in a TextBox. Consider the following XAML:
<UserControl
...
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
...>
...
<TextBox>
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyUp">
<i:InvokeCommandAction Command="{Binding MyCommand}"
CommandParameter="{Binding Text}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
...
</UserControl>
and that MyCommand is as follows:
public ICommand MyCommand {
get { return new DelegateCommand<string>(MyCommandExecute); }
}
private void MyCommandExecute(string s) { ... }
With the above, my command is invoked for every key press. How can I restrict the command to only invoke when the ENTER key is pressed?
I understand that with Expression Blend I can use Conditions but those seem to be restricted to elements and can't consider event arguments.
I have also come across SLEX which offers its own InvokeCommandAction implementation that is built on top of the Systems.Windows.Interactivity implementation and can do what I need. Another consideration is to write my own trigger, but I'm hoping there's a way to do it without using external toolkits.
There is KeyTrigger in expression blend.
<UserControl
xmlns:i="clr-namespace:System.Windows.Interactivity;
assembly=System.Windows.Interactivity"
xmlns:iex="clr-namespace:Microsoft.Expression.Interactivity.Input;
assembly=Microsoft.Expression.Interactions" ...>
<TextBox>
<i:Interaction.Triggers>
<iex:KeyTrigger Key="Enter">
<i:InvokeCommandAction Command="{Binding PasswordLoginCommand}" />
</iex:KeyTrigger>
</i:Interaction.Triggers>
</TextBox>
</UserControl>
System.Windows.Interactivity and Microsoft.Expression.Interactions assemblies are available for WPF in the official Nuget package.
I like scottrudy's approach (to which I've given a +1) with the custom triggers approach as it stays true to my initial approach. I'm including a modified version of it below to use dependency properties instead of reflection info so that it's possible to bind directly to the ICommand. I'm also including an approach using attached properties to avoid using System.Windows.Interactivity if desired. The caveat to the latter approach is that you lose the feature of multiple invokations from an event, but you can apply it more generally.
Custom Triggers Approach
ExecuteCommandAction.cs
public class ExecuteCommandAction : TriggerAction<DependencyObject> {
#region Properties
public ICommand Command {
get { return (ICommand)base.GetValue(CommandProperty); }
set { base.SetValue(CommandProperty, value); }
}
public static ICommand GetCommand(DependencyObject obj) {
return (ICommand)obj.GetValue(CommandProperty);
}
public static void SetCommand(DependencyObject obj, ICommand value) {
obj.SetValue(CommandProperty, value);
}
// We use a DependencyProperty so we can bind commands directly rather
// than have to use reflection info to find them
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(ExecuteCommandAction), null);
#endregion Properties
protected override void Invoke(object parameter) {
ICommand command = Command ?? GetCommand(AssociatedObject);
if (command != null && command.CanExecute(parameter)) {
command.Execute(parameter);
}
}
}
TextBoxEnterKeyTrigger.cs
public class TextBoxEnterKeyTrigger : TriggerBase<UIElement> {
protected override void OnAttached() {
base.OnAttached();
TextBox textBox = this.AssociatedObject as TextBox;
if (textBox != null) {
this.AssociatedObject.KeyUp += new System.Windows.Input.KeyEventHandler(AssociatedObject_KeyUp);
}
else {
throw new InvalidOperationException("This behavior only works with TextBoxes");
}
}
protected override void OnDetaching() {
base.OnDetaching();
AssociatedObject.KeyUp -= new KeyEventHandler(AssociatedObject_KeyUp);
}
private void AssociatedObject_KeyUp(object sender, KeyEventArgs e) {
if (e.Key == Key.Enter) {
TextBox textBox = AssociatedObject as TextBox;
//This checks for an mvvm style binding and updates the source before invoking the actions.
BindingExpression expression = textBox.GetBindingExpression(TextBox.TextProperty);
if (expression != null)
expression.UpdateSource();
InvokeActions(textBox.Text);
}
}
}
MyUserControl.xaml
<UserControl
...
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:b="clr-namespace:MyNameSpace.Interactivity"
...
<TextBox>
<i:Interaction.Triggers>
<b:TextBoxEnterKeyTrigger>
<b:ExecuteCommandAction Command="{Binding MyCommand}" />
</b:TextBoxEnterKeyTrigger>
</i:Interaction.Triggers>
</TextBox>
...
</UserControl>
Attached Properties Approach
EnterKeyDown.cs
public sealed class EnterKeyDown {
#region Properties
#region Command
public static ICommand GetCommand(DependencyObject obj) {
return (ICommand)obj.GetValue(CommandProperty);
}
public static void SetCommand(DependencyObject obj, ICommand value) {
obj.SetValue(CommandProperty, value);
}
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(EnterKeyDown),
new PropertyMetadata(null, OnCommandChanged));
#endregion Command
#region CommandArgument
public static object GetCommandArgument(DependencyObject obj) {
return (object)obj.GetValue(CommandArgumentProperty);
}
public static void SetCommandArgument(DependencyObject obj, object value) {
obj.SetValue(CommandArgumentProperty, value);
}
public static readonly DependencyProperty CommandArgumentProperty =
DependencyProperty.RegisterAttached("CommandArgument", typeof(object), typeof(EnterKeyDown),
new PropertyMetadata(null, OnCommandArgumentChanged));
#endregion CommandArgument
#region HasCommandArgument
private static bool GetHasCommandArgument(DependencyObject obj) {
return (bool)obj.GetValue(HasCommandArgumentProperty);
}
private static void SetHasCommandArgument(DependencyObject obj, bool value) {
obj.SetValue(HasCommandArgumentProperty, value);
}
private static readonly DependencyProperty HasCommandArgumentProperty =
DependencyProperty.RegisterAttached("HasCommandArgument", typeof(bool), typeof(EnterKeyDown),
new PropertyMetadata(false));
#endregion HasCommandArgument
#endregion Propreties
#region Event Handling
private static void OnCommandArgumentChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) {
SetHasCommandArgument(o, true);
}
private static void OnCommandChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) {
FrameworkElement element = o as FrameworkElement;
if (element != null) {
if (e.NewValue == null) {
element.KeyDown -= new KeyEventHandler(FrameworkElement_KeyDown);
}
else if (e.OldValue == null) {
element.KeyDown += new KeyEventHandler(FrameworkElement_KeyDown);
}
}
}
private static void FrameworkElement_KeyDown(object sender, KeyEventArgs e) {
if (e.Key == Key.Enter) {
DependencyObject o = sender as DependencyObject;
ICommand command = GetCommand(sender as DependencyObject);
FrameworkElement element = e.OriginalSource as FrameworkElement;
if (element != null) {
// If the command argument has been explicitly set (even to NULL)
if (GetHasCommandArgument(o)) {
object commandArgument = GetCommandArgument(o);
// Execute the command
if (command.CanExecute(commandArgument)) {
command.Execute(commandArgument);
}
}
else if (command.CanExecute(element.DataContext)) {
command.Execute(element.DataContext);
}
}
}
}
#endregion
}
MyUserControl.xaml
<UserControl
...
xmlns:b="clr-namespace:MyNameSpace.Interactivity"
...
<TextBox b:EnterKeyDown.Command="{Binding AddNewDetailCommand}"
b:EnterKeyDown.CommandArgument="{Binding Path=Text,RelativeSource={RelativeSource Self}}" />
...
</UserControl>
I ran into this same issue yesterday and solved it using custom triggers. It may seem a bit much at first, but I found this general pattern is usable for doing a lot of the things that I used to accomplish using event handlers directly in a view (like double click events). The first step is to create a trigger action that can accept a parameter since we will need it later.
public class ExecuteCommandAction : TriggerAction<FrameworkElement>
{
public string Command { get; set; }
protected override void Invoke(object o)
{
if (Command != null)
{
object ctx = AssociatedObject.DataContext;
if (ctx != null)
{
var cmd = ctx.GetType().GetProperty(Command)
.GetValue(ctx, null) as ICommand;
if (cmd != null && cmd.CanExecute(o))
{
cmd.Execute(o);
}
}
}
}
}
The next step is to create the trigger. You could do some interesting things with base classes to make it more generic for capturing different types of key presses, but we'll keep it simple.
public class TextBoxEnterKeyTrigger: TriggerBase<UIElement>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.KeyUp += AssociatedObject_KeyUp;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.KeyUp -= AssociatedObject_KeyUp;
}
void AssociatedObject_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
TextBox textBox = AssociatedObject as TextBox;
object o = textBox == null ? null : textBox.Text;
if (o != null)
{
InvokeActions(o);
}
}
}
}
Keep in mind that even though you may have a data binding in place to your TextBox value, the property changed event won't fire because your textbox hasn't lost focus. For this reason I am passing the value of the TextBox.Text property to the command. The last step is to use this feature in your XAML. You need to be sure to include the Interactivity namespace as well as the namespace that contains your code from above.
<UserControl
...
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:common="clr-namespace:My.UI;assembly=My.UI">
<TextBox Text="{Binding Path=MyText, Mode=TwoWay}" IsEnabled="{Binding CanMyCommand}">
<i:Interaction.Triggers>
<common:TextBoxEnterKeyTrigger>
<common:ExecuteCommandAction Command=MyCommand" />
</common:TextBoxEnterKeyTrigger>
</i:Interaction.Triggers>
</TextBox>
</UserControl>
I used scottrudy's code in my application however, my textbox text is bound to some property in viewmodel class and this property is not getting updated by the time command is invoked after pressiong ENTER key because my textbox hasn't lost focus yet. So, to resolved this, i added the following code snippets just above InvokeActions(o) in AssociatedObject_KeyUp method and updated text property is getting updated in viewmodel class.
BindingExpression bindingExpression = (textBox).GetBindingExpression(TextBox.TextProperty);
bindingExpression.UpdateSource();
On top of my mind.. You can pass event args to command and than in ViewModel check if e.KeyPress = Keys.Enter.. this is not really code :) i dont have my VS on this computer.. this is rather an idea :)
I would like to use the ScrollToVerticalOffset method of a ScrollViewer to go to the top of the scrollviewer.
But with a MVVM approch.
I think I have to create a dependency property to take this behavior.
EDIT :
The behavior is :
public class ScrollPositionBehavior : Behavior<FrameworkElement>
{
public double Position
{
get { return (double)GetValue(PositionProperty); }
set { SetValue(PositionProperty, value); }
}
public static readonly DependencyProperty PositionProperty = DependencyProperty.Register("Position", typeof(double), typeof(ScrollPositionBehavior), new PropertyMetadata((double)0, new PropertyChangedCallback(OnPositionChanged)));
protected override void OnAttached()
{
base.OnAttached();
}
private static void OnPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ScrollPositionBehavior behavior = d as ScrollPositionBehavior;
double value = (double)e.NewValue;
((ScrollViewer)(behavior.AssociatedObject)).ScrollToVerticalOffset(value);
}
protected override void OnDetaching()
{
base.OnDetaching();
}
}
used like :
<ScrollViewer>
<Interactivity:Interaction.Behaviors>
<fxBehavior:ScrollPositionBehavior Position="{Binding Position}" />
</Interactivity:Interaction.Behaviors>
<other things ...>
</ScrollViewer>
with
xmlns:Interactivity="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:fxBehavior="clr-namespace:MyNamespace.Behavior;assembly=MyAssembly"
I have a parser xaml exception :
this is a : AG_E_PARSER_BAD_PROPERTY_VALUE
Note that i'm using the behavior based on a FrameworkElement, as I'm using silverlight 3 (in fact, this is SL for WP7). I've seen that the binding should work only for FrameworkElement.
Thanks in advance for your help
You were on the right way. First of all you need to change your OnPositionChanged method to find out which instance of the behavior had its Position changed:
private static void OnPositionChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
ScrollPositionBehavior behavior = d as ScrollPositionBehavior;
double value = (double)e.NewValue;
behavior.AssociatedObject.ScrollToVerticalOffset(value);
}
Then, you'll get the ScrollViewer as associated object when you attach the behavior to it:
<ScrollViewer>
<i:Interaction.Behaviors>
<my:ScrollPositionBehavior Position="{what you need, e.g. Binding}" />
</i:Interaction.Behaviors>
</ScrollViewer>
Note that if you use a Binding it can be a OneWay binding, because the Position will never get updated by the behavior itself.
I want to set the UpdateSourceTrigger to an event of a control:
<TextBox Text="{Binding Field, UpdateSourceMode=btnOK.Click}">
<Button Name="btnOK">
<Button.Triggers>
<Trigger>
<!-- Update source -->
</Trigger>
</Button.Triggers>
</Button>
I thought about two ways:
Set UpdateSourceMode or some other stuff in the binding.
Set an EventTrigger that updates source on button click.
Possible, or I have to do it with code?
You'll have to use code. Specifically:
Set UpdateSourceTrigger=Explicit on the TextBox.
Call UpdateSource when the user clicks the Button.
However, you can put the code in the code behind or in an attached behavior.
I know it's been a while, but I came across the same issue and want to share my solution. Hope it will be helpful for somebody.
public class UpdateSourceBehavior : Behavior<System.Windows.Interactivity.TriggerBase>
{
internal const string TargetElementPropertyLabel = "TargetElement";
static UpdateSourceBehavior()
{
TargetElementProperty = DependencyProperty.Register
(
TargetElementPropertyLabel,
typeof(FrameworkElement),
typeof(UpdateSourceBehavior),
new PropertyMetadata(null)
);
}
public static readonly DependencyProperty TargetElementProperty;
[Bindable(true)]
public FrameworkElement TargetElement
{
get { return (FrameworkElement)base.GetValue(TargetElementProperty); }
set { base.SetValue(TargetElementProperty, value); }
}
public PropertyPath TargetProperty { get; set; }
protected override void OnAttached()
{
base.OnAttached();
this.InitializeMembers();
base.AssociatedObject.PreviewInvoke += this.AssociatedObject_PreviewInvoke;
}
protected override void OnDetaching()
{
base.AssociatedObject.PreviewInvoke -= this.AssociatedObject_PreviewInvoke;
base.OnDetaching();
}
private void AssociatedObject_PreviewInvoke(object sender, PreviewInvokeEventArgs e)
{
this.m_bindingExpression.UpdateSource();
}
private void InitializeMembers()
{
if (this.TargetElement != null)
{
var targetType = this.TargetElement.GetType();
var fieldInfo = targetType.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)
.FirstOrDefault(fi => fi.Name == this.TargetProperty.Path + "Property");
if (fieldInfo != null)
this.m_bindingExpression = this.TargetElement.GetBindingExpression((DependencyProperty)fieldInfo.GetValue(null));
else
throw new ArgumentException(string.Format("{0} doesn't contain a DependencyProperty named {1}.", targetType, this.TargetProperty.Path));
}
else
throw new InvalidOperationException("TargetElement must be assigned to in order to resolve the TargetProperty.");
}
private BindingExpression m_bindingExpression;
}
Here is my solution:
XAML:
<StackPanel>
<i:Interaction.Triggers>
<i:EventTrigger SourceName="submit" EventName="Click">
<behaviours:TextBoxUpdateSourceAction TargetName="searchBox"></behaviours:TextBoxUpdateSourceAction>
</i:EventTrigger>
</i:Interaction.Triggers>
<TextBox x:Name="searchBox">
<TextBox.Text>
<Binding Path="SomeProperty" UpdateSourceTrigger="Explicit" NotifyOnValidationError="True">
<Binding.ValidationRules>
<DataErrorValidationRule ValidatesOnTargetUpdated="False"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Button x:Name="submit"></Button>
</StackPanel>
Behaviour definition (inherited from TargetedTriggerAction):
public class TextBoxUpdateSourceAction : TargetedTriggerAction<TextBox>
{
protected override void Invoke(object parameter)
{
BindingExpression be = Target.GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();
}
}
Please note that it's important to attach TextBoxUpdateSourceAction to parent container (StackPanel in example code).