I am trying to add a dependency property IsActive to a RubberBandBehavior I found on CodeProject, so that I can activate and deactivate it from my ViewModel. The code below does not give any compile error and runs, but the value does not seem to be set correctly, when I check the line with the comment \\ this line is always 'false'.
The modified class RubberBandBehavior:
public class RubberBandBehavior : Behavior<ListBox>
{
public static readonly DependencyProperty IsActiveProperty =
DependencyProperty.Register("IsActive", typeof(bool), typeof(RubberBandBehavior),
new PropertyMetadata(IsActiveProperty_Changed));
private static void IsActiveProperty_Changed(DependencyObject sender,
DependencyPropertyChangedEventArgs args)
{
// This gets called _after_ OnAttached!
}
public bool IsActive
{
get { return (bool)GetValue(IsActiveProperty); }
set { SetValue(IsActiveProperty, value); }
}
protected override void OnAttached()
{
AssociatedObject.Loaded += new System.Windows.RoutedEventHandler(AssociatedObject_Loaded);
base.OnAttached();
}
void AssociatedObject_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
bool a = IsActive; // this line is always 'false'
RubberBandAdorner band = new RubberBandAdorner(AssociatedObject);
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(AssociatedObject);
adornerLayer.Add(band);
}
protected override void OnDetaching()
{
AssociatedObject.Loaded -= new System.Windows.RoutedEventHandler(AssociatedObject_Loaded);
base.OnDetaching();
}
}
In my XAML I have this:
<i:Interaction.Behaviors>
<behavior:RubberBandBehavior IsActive="True"/>
</i:Interaction.Behaviors>
My plan is then to bind to my ViewModel like this, but first I need to get the above sorted out:
<i:Interaction.Behaviors>
<behavior:RubberBandBehavior IsActive="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.IsEditable}"/>
</i:Interaction.Behaviors>
You wont get this value in the AssociatedObject.Loaded event. You need to use the IsActiveProperty_Changed handler in your code example to get the correct value of this property
Related
is it possible to bind an UWP CommandBar to something like a ObservableCollection or so?
What i want to achieve ist to bind my CommandBar of my NavigationView to an Object of a specific Page so that the AppBarButton change dynamicaly depending on the current Page
What i tryed:
MainPage.xaml
<NavigationView.HeaderTemplate>
<DataTemplate>
<Grid>
<CommandBar Grid.Column="1"
HorizontalAlignment="Right"
VerticalAlignment="Top"
DefaultLabelPosition="Right"
Background="{ThemeResource SystemControlBackgroundAltHighBrush}" Content="{Binding Path=Content.AppBarButtonList, ElementName=rootFrame}">
</CommandBar>
</Grid>
</DataTemplate>
</NavigationView.HeaderTemplate>
SomePage.xaml.cs
public ObservableCollection<AppBarButton> AppBarButtonList = new ObservableCollection<AppBarButton> {
new AppBarButton { Icon = new SymbolIcon(Symbol.Accept), Label="Bla" },
new AppBarButton{Icon=new SymbolIcon(Symbol.Add),Label="Add"}
};
But the CommandBar shows nothing.
Thanks.
My original solution was using the PrimaryCommands property to bind the commands, but it turns out this property is read-only.
My solution to the problem will be using behaviors.
First add a reference to Microsoft.Xaml.Behaviors.Uwp.Managed from NuGet.
Then add the following behavior to your project:
public class BindableCommandBarBehavior : Behavior<CommandBar>
{
public ObservableCollection<AppBarButton> PrimaryCommands
{
get { return (ObservableCollection<AppBarButton>)GetValue(PrimaryCommandsProperty); }
set { SetValue(PrimaryCommandsProperty, value); }
}
public static readonly DependencyProperty PrimaryCommandsProperty = DependencyProperty.Register(
"PrimaryCommands", typeof(ObservableCollection<AppBarButton>), typeof(BindableCommandBarBehavior), new PropertyMetadata(default(ObservableCollection<AppBarButton>), UpdateCommands));
private static void UpdateCommands(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
if (!(dependencyObject is BindableCommandBarBehavior behavior)) return;
var oldList = dependencyPropertyChangedEventArgs.OldValue as ObservableCollection<AppBarButton>;
if (dependencyPropertyChangedEventArgs.OldValue != null)
{
oldList.CollectionChanged -= behavior.PrimaryCommandsCollectionChanged;
}
var newList = dependencyPropertyChangedEventArgs.NewValue as ObservableCollection<AppBarButton>;
if (dependencyPropertyChangedEventArgs.NewValue != null)
{
newList.CollectionChanged += behavior.PrimaryCommandsCollectionChanged;
}
behavior.UpdatePrimaryCommands();
}
private void PrimaryCommandsCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
UpdatePrimaryCommands();
}
private void UpdatePrimaryCommands()
{
if (PrimaryCommands != null)
{
AssociatedObject.PrimaryCommands.Clear();
foreach (var command in PrimaryCommands)
{
AssociatedObject.PrimaryCommands.Add(command);
}
}
}
protected override void OnDetaching()
{
base.OnDetaching();
if (PrimaryCommands != null)
{
PrimaryCommands.CollectionChanged -= PrimaryCommandsCollectionChanged;
}
}
}
This behavior essentially creates a fake PrimaryCommands property that is bindable and also observes collection changed events. Whenever a change occurs, the commands are rebuilt.
Finally, the problem in your code is that your AppBarButtonList is just a field, not a property. Change it like this:
public ObservableCollection<AppBarButton> AppBarButtonList { get; } = new ObservableCollection<AppBarButton> {
new AppBarButton { Icon = new SymbolIcon(Symbol.Accept), Label="Bla" },
new AppBarButton{Icon=new SymbolIcon(Symbol.Add),Label="Add"}
};
Notice the {get ;} which was added before the assignment operator.
Now you can use the behavior in XAML like this:
<CommandBar>
<interactivity:Interaction.Behaviors>
<local:BindableCommandBarBehavior PrimaryCommands="{Binding Path=Content.AppBarButtonList, ElementName=rootFrame}" />
</interactivity:Interaction.Behaviors>
</CommandBar>
This is by no means a perfect solution and could be improved upon to allow different collection types binding and more, but it should cover your scenario. An alternative solution would be to implement a custom version of command bar, with new additional dependency property directly on the type, but I used behavior to make it clearer for the user that this is an "added" functionality, not a built-in one.
I found this answer very helpful. I did some more adjustments, like using a DataTemplateSelector to remove UI references like "AppBarButton" from the bindable data source.
public class BindableCommandBarBehavior : Behavior<CommandBar>
{
public static readonly DependencyProperty PrimaryCommandsProperty = DependencyProperty.Register(
"PrimaryCommands", typeof(object), typeof(BindableCommandBarBehavior),
new PropertyMetadata(null, UpdateCommands));
public static readonly DependencyProperty ItemTemplateSelectorProperty = DependencyProperty.Register(
"ItemTemplateSelector", typeof(DataTemplateSelector), typeof(BindableCommandBarBehavior),
new PropertyMetadata(null, null));
public DataTemplateSelector ItemTemplateSelector
{
get { return (DataTemplateSelector)GetValue(ItemTemplateSelectorProperty); }
set { SetValue(ItemTemplateSelectorProperty, value); }
}
public object PrimaryCommands
{
get { return GetValue(PrimaryCommandsProperty); }
set { SetValue(PrimaryCommandsProperty, value); }
}
protected override void OnDetaching()
{
base.OnDetaching();
if (PrimaryCommands is INotifyCollectionChanged notifyCollectionChanged)
{
notifyCollectionChanged.CollectionChanged -= PrimaryCommandsCollectionChanged;
}
}
private void UpdatePrimaryCommands()
{
if (AssociatedObject == null)
return;
if (PrimaryCommands == null)
return;
AssociatedObject.PrimaryCommands.Clear();
if (!(PrimaryCommands is IEnumerable enumerable))
{
AssociatedObject.PrimaryCommands.Clear();
return;
}
foreach (var command in enumerable)
{
var template = ItemTemplateSelector.SelectTemplate(command, AssociatedObject);
if (!(template?.LoadContent() is FrameworkElement dependencyObject))
continue;
dependencyObject.DataContext = command;
if (dependencyObject is ICommandBarElement icommandBarElement)
AssociatedObject.PrimaryCommands.Add(icommandBarElement);
}
}
protected override void OnAttached()
{
base.OnAttached();
UpdatePrimaryCommands();
}
private void PrimaryCommandsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
UpdatePrimaryCommands();
}
private static void UpdateCommands(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
if (!(dependencyObject is BindableCommandBarBehavior behavior)) return;
if (dependencyPropertyChangedEventArgs.OldValue is INotifyCollectionChanged oldList)
{
oldList.CollectionChanged -= behavior.PrimaryCommandsCollectionChanged;
}
if (dependencyPropertyChangedEventArgs.NewValue is INotifyCollectionChanged newList)
{
newList.CollectionChanged += behavior.PrimaryCommandsCollectionChanged;
}
behavior.UpdatePrimaryCommands();
}
}
The DataTemplateSelector:
public class CommandBarMenuItemTemplateSelector : DataTemplateSelector
{
public DataTemplate CbMenuItemTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
if (item is ContextAction)
{
return CbMenuItemTemplate;
}
return base.SelectTemplateCore(item, container);
}
}
Xaml for templates:
<DataTemplate x:Key="CbMenuItemTemplate">
<AppBarButton
Command="{Binding Command}"
Icon="Add"
Label="{Binding Text}" />
</DataTemplate>
<viewLogic:CommandBarMenuItemTemplateSelector x:Key="CommandBarMenuItemTemplateSelector"
CbMenuItemTemplate="{StaticResource CbMenuItemTemplate}" />
Usage:
<CommandBar>
<interactivity:Interaction.Behaviors>
<viewLogic:BindableCommandBarBehavior ItemTemplateSelector="{StaticResource CommandBarMenuItemTemplateSelector}" PrimaryCommands="{Binding ContextActions}" />
</interactivity:Interaction.Behaviors>
</CommandBar>
Where ContextActions is a ObservableCollection of my class ContextAction.
I have a custom behavior that take a boolean as parameter. I'm trying to bind it to a property in my model but I can't get it to work. (I'm using DevExpress)
<dxg:GridControl
x:Name="dgArticles"
Grid.Row="1"
AutoGenerateColumns="None"
ItemsSource="{Binding Path=Articles, Mode=TwoWay}">
<dxmvvm:Interaction.Behaviors>
<util:ExpandAllBehavior HasLotPartie="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.CahierDesCharges.HasLotPartie }" />
</dxmvvm:Interaction.Behaviors>
</dxg:GridControl
This is a WPF UserControl and the DataContext is defined in code-behind as follow:
public partial class GridView : UserControl
{
public GridView(ArticleViewModel a)
{
InitializeComponent();
this.DataContext = a;
}
}
My ViewModel:
public class ArticleViewModel : INotifyPropertyChanged
{
private Cahier cahierDesCharges;
public Cahier CahierDesCharges { get { return cahierDesCharges; } set { } }
private ObservableCollection<Article> articles;
public ObservableCollection<Article> Articles
{
get { return articles; }
set { articles = value; OnPropertyChanged("articles"); }
}
public ArticleViewModel() { }
public ArticleViewModel(Cahier c)
{
this.cahierDesCharges = c;
this.articles = CahierDesCharges.Articles;
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this,
new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
And the CahierDesCharges Class:
public class Cahier : INotifyPropertyChanged
{
public bool HasLotPartie { get; set; }
private ObservableCollection<Article> articles;
public ObservableCollection<Article> Articles
{
get { return articles; }
set { articles = value; OnPropertyChanged("articles"); }
}
public event PropertyChangedEventHandler PropertyChanged;
public Cahier() { }
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this,
new System.ComponentModel.PropertyChangedEventArgs(propertyName));
Console.WriteLine(propertyName);
}
}
The Behavior:
public class ExpandAllBehavior : Behavior<GridControl>
{
public static readonly DependencyProperty HasLotPartieProperty =
DependencyProperty.Register("HasLotPartie", typeof(Boolean), typeof(ExpandAllBehavior));
public bool HasLotPartie
{
get { return (bool)GetValue(HasLotPartieProperty); }
set { SetValue(HasLotPartieProperty, value); }
}
protected override void OnAttached()
{
if (HasLotPartie)
{
base.OnAttached();
this.AssociatedObject.CustomRowFilter += AssociatedObject_Loaded;
}
}
void AssociatedObject_Loaded(object sender, EventArgs e)
{
int dataRowCount = AssociatedObject.VisibleRowCount;
for (int rowHandle = 0; rowHandle < dataRowCount; rowHandle++)
AssociatedObject.ExpandMasterRow(rowHandle);
}
protected override void OnDetaching()
{
base.OnDetaching();
}
}
I have used the line "{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.CahierDesCharges.HasLotPartie }" at a different place and I know it's working. In the following example it's working perfectly fine:
<dxg:GridControl.View>
<dxg:TableView
AllowScrollAnimation="True"
EnableImmediatePosting="True"
IsDetailButtonVisibleBinding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.CahierDesCharges.HasLotPartie }"
Name="view"
ShowGroupedColumns="False"
ShowGroupPanel="False"
UseLightweightTemplates="None" />
</dxg:GridControl.View>
The behavior works fine if I manually set the value to true: <util:ExpandAllBehavior HasLotPartie="True" /> So I thought that it might be that the DadaContext is not inherited and I use the trick explain by Thomas Levesque on his blog here and this is what I got:
<dxg:GridControl.Resources>
<util:BindingProxy x:Key="proxy" Data="{Binding}" />
</dxg:GridControl.Resources>
<dxmvvm:Interaction.Behaviors>
<util:ExpandAllBehavior HasLotPartie="{Binding Data.CahierDesCharges.HasLotPartie, Source={StaticResource proxy}}" />
</dxmvvm:Interaction.Behaviors>
I've used this trick several time and I know it works too but not in this scenario. Right now I'm stuck with this after several hours of searching so any help is very welcome.
Thank you
Ok so I figured that my CahierDesCharges was null when the behavior was called. Therefore HasLotPartie was false no matter what and so it did not reach my event. So I just moved my if() in AssociatedObject_Loaded method and it worked. I needed to wait until the GridControl was fully loaded so the CahierDesCharges wasn't null anymore.
This is my behavior class now:
public class ExpandAllBehavior : Behavior<GridControl>
{
public static readonly DependencyProperty HasLotPartieProperty =
DependencyProperty.Register("HasLotPartie", typeof(bool), typeof(ExpandAllBehavior));
public bool HasLotPartie
{
get { return (bool)GetValue(HasLotPartieProperty); }
set { SetValue(HasLotPartieProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.Loaded += AssociatedObject_Loaded; //Apllied after GridControl is fully loaded.
this.AssociatedObject.CustomRowFilter += AssociatedObject_Loaded; //Applied after filtering on Rows
}
void AssociatedObject_Loaded(object sender, EventArgs e)
{
if (HasLotPartie)
{
int dataRowCount = AssociatedObject.VisibleRowCount;
for (int rowHandle = 0; rowHandle < dataRowCount; rowHandle++)
AssociatedObject.ExpandMasterRow(rowHandle);
}
}
protected override void OnDetaching()
{
base.OnDetaching();
}
}
Now my MasterDetails are always expanded when HasLotPartie is true which was the wanted effect.
As always I was stuck focussing on complicated stuff while the solution was easy.
I have created a very simple example to show my problem. Maybe I just think in a wrong way.
I want to select an Item of my TreeView - and I would like to see it in the View (Blue background).
To realize the TwoWayBinding I use this Behavior: Data binding to SelectedItem in a WPF Treeview
public class BindableSelectedItemBehavior : Behavior<TreeView>
{
#region SelectedItem Property
public object SelectedItem
{
get { return (object)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(object), typeof(BindableSelectedItemBehavior), new UIPropertyMetadata(null, OnSelectedItemChanged));
private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var item = e.NewValue as TreeViewItem;
if (item != null)
{
item.SetValue(TreeViewItem.IsSelectedProperty, true);
}
}
#endregion
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
if (this.AssociatedObject != null)
{
this.AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged;
}
}
private void OnTreeViewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
this.SelectedItem = e.NewValue;
}
}
But if I click on an Item it does not go into the 'if' of the OnSelectedItemChanged because e.newValue as TreeViewItem is null
My XAML is very simple:
<StackPanel>
<TreeView xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
ItemsSource="{Binding Items}">
<i:Interaction.Behaviors>
<local:BindableSelectedItemBehavior
SelectedItem="{Binding Item}" />
</i:Interaction.Behaviors>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate>
<TextBlock Text="{Binding Text}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<TextBox Text="{Binding Item.Text}"/>
</StackPanel>
Thank you guys!
Just for the sake of the convenience, here's the final solution combined of the OP and ghrod's answer:
namespace MyPoject.Behaviors
{
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
public class BindableSelectedItemBehavior : Behavior<TreeView>
{
#region SelectedItem Property
public object SelectedItem
{
get { return (object)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register(
nameof(SelectedItem),
typeof(object),
typeof(BindableSelectedItemBehavior),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
OnSelectedItemChanged));
static void OnSelectedItemChanged(DependencyObject sender,
DependencyPropertyChangedEventArgs e)
{
var behavior = (BindableSelectedItemBehavior)sender;
var generator = behavior.AssociatedObject.ItemContainerGenerator;
if (generator.ContainerFromItem(e.NewValue) is TreeViewItem item)
item.SetValue(TreeViewItem.IsSelectedProperty, true);
}
#endregion
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
if (this.AssociatedObject != null)
AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged;
}
void OnTreeViewSelectedItemChanged(object sender,
RoutedPropertyChangedEventArgs<object> e) =>
SelectedItem = e.NewValue;
}
}
SelectedItem property of TreeView does not return TreeViewItem in your case. It returns currently selected item from your bound Items collection. To get TreeViewItem from SelectedItem, you need to use ItemContainerGenerator here:
private static void OnSelectedItemChanged(DependencyObject sender,
DependencyPropertyChangedEventArgs e)
{
var behavior = (BindableSelectedItemBehavior)sender;
var generator = behavior.AssociatedObject.ItemContainerGenerator;
var item = generator.ContainerFromItem(e.NewValue) as TreeViewItem;
if (item != null)
{
item.SetValue(TreeViewItem.IsSelectedProperty, true);
}
}
Your OnSelectedItemChanged will only pass an object of type TreeViewItem if your actual model objects are of type TreeViewItem, which isn't going to be the case 99% of the time. You will instead have to retrieve TreeViewItem from a model object, but it will not be available if the node is currently collapsed, which makes selecting collapsed nodes very non-trivial.
I've made an effort to explain this very thoroughly in my blog, including code samples here
I have a few views that each have several XAML TextBox instances. The Text property of each is bound to a value object that represents the visual data model for the view.
<TextBox Text="{Binding Path=SelectedItem.SomeValue, UpdateSourceTrigger=PropertyChanged}"/>
I have about 9 or 10 of these boxes in a form. I have a class (ChangeModel) that keeps track of which forms have been altered (e.g. the user has entered in a new value). The problem is the actual value object that is bound to the TextBox.Text property (in the example that would be SelectedItem.SomeValue) can't access the ChangeModel.
I'd like to easily add a binding in the XML (maybe in the resources section) that will call a command in the view model whenever any TextBox changes. I think I can do this with a DataTrigger statement but I'm not sure how to go about it.
Can anyone describe how to use a data trigger or any other XAML mechanism to alert the view model whenever any TextBox within that view is altered?
Alternatively to that Markus Hütter said you can save a few lines of XAML and write custom behavior like this
public class InvokeCommandOnTextChanged : Behavior<TextBox>
{
public static DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(InvokeCommandOnTextChanged));
public static DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(InvokeCommandOnTextChanged));
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.TextChanged += OnTextChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.TextChanged -= OnTextChanged;
}
private void OnTextChanged(object sender, TextChangedEventArgs e)
{
var command = this.Command;
var param = this.CommandParameter;
if (command != null && command.CanExecute(param))
{
command.Execute(param);
}
}
}
Then you can use this behavior with your textboxes:
<TextBox>
<i:Interaction.Behaviors>
<b:InvokeCommandOnTextChanged Command="{Binding AddCommand}" />
</i:Interaction.Behaviors>
</TextBox>
Is there a way to get a TextBox in Windows Phone 7 to update the Binding as the user types each letter rather than after losing focus?
Like the following WPF TextBox would do:
<TextBox Text="{Binding Path=TextProperty, UpdateSourceTrigger=PropertyChanged}"/>
Silverlight for WP7 does not support the syntax you've listed. Do the following instead:
<TextBox TextChanged="OnTextBoxTextChanged"
Text="{Binding MyText, Mode=TwoWay,
UpdateSourceTrigger=Explicit}" />
UpdateSourceTrigger = Explicit is a smart bonus here. What is it? Explicit: Updates the binding source only when you call the UpdateSource method. It saves you one extra binding set when the user leaves the TextBox.
In C#:
private void OnTextBoxTextChanged( object sender, TextChangedEventArgs e )
{
TextBox textBox = sender as TextBox;
// Update the binding source
BindingExpression bindingExpr = textBox.GetBindingExpression( TextBox.TextProperty );
bindingExpr.UpdateSource();
}
I like using an attached property. Just in case you're into those little buggers.
<toolkit:DataField Label="Name">
<TextBox Text="{Binding Product.Name, Mode=TwoWay}" c:BindingUtility.UpdateSourceOnChange="True"/>
</toolkit:DataField>
And then the backing code.
public class BindingUtility
{
public static bool GetUpdateSourceOnChange(DependencyObject d)
{
return (bool)d.GetValue(UpdateSourceOnChangeProperty);
}
public static void SetUpdateSourceOnChange(DependencyObject d, bool value)
{
d.SetValue(UpdateSourceOnChangeProperty, value);
}
// Using a DependencyProperty as the backing store for …
public static readonly DependencyProperty
UpdateSourceOnChangeProperty =
DependencyProperty.RegisterAttached(
"UpdateSourceOnChange",
typeof(bool),
typeof(BindingUtility),
new PropertyMetadata(false, OnPropertyChanged));
private static void OnPropertyChanged (DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var textBox = d as TextBox;
if (textBox == null)
return;
if ((bool)e.NewValue)
{
textBox.TextChanged += OnTextChanged;
}
else
{
textBox.TextChanged -= OnTextChanged;
}
}
static void OnTextChanged(object s, TextChangedEventArgs e)
{
var textBox = s as TextBox;
if (textBox == null)
return;
var bindingExpression = textBox.GetBindingExpression(TextBox.TextProperty);
if (bindingExpression != null)
{
bindingExpression.UpdateSource();
}
}
}
Not through binding syntax, no, but it's easy enough without. You have to handle the TextChanged event and call UpdateSource on the binding.
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
((TextBox) sender).GetBindingExpression( TextBox.TextProperty ).UpdateSource();
}
This can be converted into an attached behavior as well pretty easily.
In TextChanged event call UpdateSource().
BindingExpression be = itemNameTextBox.GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();
You can write your own TextBox Behavior to handle Update on TextChanged:
This is my sample to PasswordBox but you can simple change it to handle any property of the any object.
public class UpdateSourceOnPasswordChangedBehavior
: Behavior<PasswordBox>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PasswordChanged += OnPasswordChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.PasswordChanged -= OnPasswordChanged;
}
private void OnPasswordChanged(object sender, RoutedEventArgs e)
{
AssociatedObject.GetBindingExpression(PasswordBox.PasswordProperty).UpdateSource();
}
}
Ussage:
<PasswordBox x:Name="Password" Password="{Binding Password, Mode=TwoWay}" >
<i:Interaction.Behaviors>
<common:UpdateSourceOnPasswordChangedBehavior/>
</i:Interaction.Behaviors>
</PasswordBox>
UpdateSourceTrigger=Explicit doesnt work for me, hence Im using custom class derivated from TextBox
public class TextBoxEx : TextBox
{
public TextBoxEx()
{
TextChanged += (sender, args) =>
{
var bindingExpression = GetBindingExpression(TextProperty);
if (bindingExpression != null)
{
bindingExpression.UpdateSource();
}
};
}
}
It's just one line of code!
(sender as TextBox).GetBindingExpression(TextBox.TextProperty).UpdateSource();
You can create a generic TextChanged event (for example "ImmediateTextBox_TextChanged") in the code behind of your page, and than link it to any TextBox in the page.
I took Praetorian's answer and made an extension class that inherits TextBox so you don't have to muddle up your view's code behind with this behavior.
C-Sharp:
public class TextBoxUpdate : TextBox
{
public TextBoxUpdate()
{
TextChanged += OnTextBoxTextChanged;
}
private void OnTextBoxTextChanged(object sender, TextChangedEventArgs e)
{
TextBox senderText = (TextBox)sender;
BindingExpression bindingExp = senderText.GetBindingExpression(TextBox.TextProperty);
bindingExp.UpdateSource();
}
}
VisualBasic:
Public Class TextBoxUpdate : Inherits TextBox
Private Sub OnTextBoxTextChanged(sender As Object, e As TextChangedEventArgs) Handles Me.TextChanged
Dim senderText As TextBox = DirectCast(sender, TextBox)
Dim bindingExp As BindingExpression = senderText.GetBindingExpression(TextBox.TextProperty)
bindingExp.UpdateSource()
End Sub
End Class
Then call like this in XAML:
<local:TextBoxUpdate Text="{Binding PersonName, Mode=TwoWay}"/>