I have a OneWayToSource binding that is not behaving as I expected when I set the DataContext of the target control. The property of the source is being set to default instead of the value of the target control's property.
I've created a very simple program in a standard WPF window that illustrates my problem:
XAML
<StackPanel>
<TextBox x:Name="tb"
Text="{Binding Path=Text,Mode=OneWayToSource,UpdateSourceTrigger=PropertyChanged}"
TextChanged="TextBox_TextChanged"/>
<Button Content="Set DataContext" Click="Button1_Click"/>
</StackPanel>
MainWindow.cs
public partial class MainWindow : Window
{
private ViewModel _vm = new ViewModel();
private void Button1_Click(object sender, RoutedEventArgs e)
{
Debug.Print("'Set DataContext' button clicked");
tb.DataContext = _vm;
}
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
Debug.Print("TextBox changed to " + tb.Text);
}
}
ViewModel.cs
public class ViewModel
{
private string _Text;
public string Text
{
get { return _Text; }
set
{
Debug.Print(
"ViewModel.Text (old value=" + (_Text ?? "<null>") +
", new value=" + (value ?? "<null>") + ")");
_Text = value;
}
}
}
The TextBox tb starts out with a null DataContext and therefore the binding is not expected to do anything. So if I type something in the text box, say "X", the ViewModel.Text property remains null.
If I then click the Set DataContext button I would have expected the ViewModel.Text property to be set to the "X" of the TextBox.Text property. Instead it is set to "". Certainly the binding is working because if I then type "Y" in the text box, after the "X", it sets the ViewModel.Text property to "XY".
Here is an example of the output (the last two lines are counter-intuitive because of the order of evaluation, but they definitely both occur immediately after typing the "Y"):
TextBox changed to X
'Set DataContext' button clicked
ViewModel.Text (old value=<null>, new value=)
ViewModel.Text (old value=, new value=XY)
TextBox changed to XY
Why is the ViewModel.Text property being set to "" instead of "X" when the DataContext is set?
What am I doing wrong? Am I missing something? Have I misunderstood something about binding?
Edit: I would have expected the output to be:
TextBox changed to X
'Set DataContext' button clicked
ViewModel.Text (old value=<null>, new value=X)
ViewModel.Text (old value=X, new value=XY)
TextBox changed to XY
Its a bug or perhabs not. Microsoft claims its by design. You first type x and then you kill DataContext by clicking on Button hence why the TextBox holds x and your viewModel.Text property gets newly initialized (its empty). When on datacontext changed getter will still be called. In the end you have no chance to fix this.
You can however use two way and let it be.
Here you will have to UpdateSource like below:
private void Button1_Click(object sender, RoutedEventArgs e)
{
Debug.Print("'Set DataContext' button clicked");
tb.DataContext = _vm;
var bindingExp = tb.GetBindingExpression(TextBox.TextProperty);
bingExp.UpdateSource();
}
TextBox has a Binding in it's TextProperty and when you set TextBox's DataContext, TextBox will update it's source (viewmodel.Text) , no matter which type of the UpdateSourceTrigger.
It's said that the first output in viewmodel
"ViewModel.Text (old value=<null>, new value=)"
is not triggered by UpdateSourceTrigger=PropertyChanged.
It's just a process of init:
private string _Text;
public string Text
{
get { return _Text; }
set
{
Debug.Print(
"ViewModel.Text (old value=" + (_Text ?? "<null>") +
", new value=" + (value ?? "<null>") + ")");
_Text = value;
}
}
Because it's not triggered by UpdateSourceTrigger=PropertyChanged, the viewmodel will not know the value of TextBox.Text.
When you type "Y",the trigger of PropertyChanged will working,so the viewmodel read text of TextBox.
There is a bug in .NET 4 with one way to source bindings that it calls getter for OneWayToSource bindings thats why you are having this problem.You can verify it by putting breakpoint on tb.DataContext = _vm; you will find setter is called and just after that getter is called on Text property.You can resolve your problem by manually feeding the viewmodel values from view before assigning the datacontext..NET 4.5 resolves this issue.
see here and here too
private void Button1_Click(object sender, RoutedEventArgs e)
{
Debug.Print("'Set DataContext' button clicked");
_vm.Text=tb.Text;
tb.DataContext = _vm;
}
You need Attached property:
public static readonly DependencyProperty OneWaySourceRaiseProperty = DependencyProperty.RegisterAttached("OneWaySourceRaise", typeof(object), typeof(FrameworkElementExtended), new FrameworkPropertyMetadata(OneWaySourceRaiseChanged));
public static object GetOneWaySourceRaise(DependencyObject o)
{
return o.GetValue(OneWaySourceRaiseProperty);
}
public static void SetOneWaySourceRaise(DependencyObject o, object value)
{
o.SetValue(OneWaySourceRaiseProperty, value);
}
private static void OneWaySourceRaiseChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == null)
return;
var target = (FrameworkElement)d;
target.Dispatcher.InvokeAsync(() =>
{
var bindings = target.GetBindings().Where(i => i.ParentBinding?.Mode == BindingMode.OneWayToSource).ToArray();
foreach (var i in bindings)
{
i.DataItem.SetProperty(i.ParentBinding.Path.Path, d.GetValue(i.TargetProperty));
}
});
And set binding in XAML:
extendends:FrameworkElementExtended.OneWaySourceRaise="{Binding}"
where {Binding} - is binding to DataContext.
You need:
public static IEnumerable<BindingExpression> GetBindings<T>(this T element, Func<DependencyProperty, bool> func = null) where T : DependencyObject
{
var properties = element.GetType().GetDependencyProperties();
foreach (var i in properties)
{
var binding = BindingOperations.GetBindingExpression(element, i);
if (binding == null)
continue;
yield return binding;
}
}
private static readonly ConcurrentDictionary<Type, DependencyProperty[]> DependencyProperties = new ConcurrentDictionary<Type, DependencyProperty[]>();
public static DependencyProperty[] GetDependencyProperties(this Type type)
{
return DependencyProperties.GetOrAdd(type, t =>
{
var properties = GetDependencyProperties(TypeDescriptor.GetProperties(type, new Attribute[] { new PropertyFilterAttribute(PropertyFilterOptions.All) }));
return properties.ToArray();
});
}
private static IEnumerable<DependencyProperty> GetDependencyProperties(PropertyDescriptorCollection collection)
{
if (collection == null)
yield break;
foreach (PropertyDescriptor i in collection)
{
var dpd = DependencyPropertyDescriptor.FromProperty(i);
if (dpd == null)
continue;
yield return dpd.DependencyProperty;
}
}
Related
I've created a custom control that extends the RichTextBox so that I can create a binding for the xaml property. It all works well as long as I just update the property from the viewmodel but when I try to edit in the richtextbox the property is not updated back.
I have the following code in the extended version of the richtextbox.
public static readonly DependencyProperty TextProperty = DependencyProperty.Register ("Text", typeof(string), typeof(BindableRichTextBox), new PropertyMetadata(OnTextPropertyChanged));
private static void OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var rtb = d as BindableRichTextBox;
if (rtb == null)
return;
string xaml = null;
if (e.NewValue != null)
{
xaml = e.NewValue as string;
if (xaml == null)
return;
}
rtb.Xaml = xaml ?? string.Empty;
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
In the view I've set the binding like such
<Controls:BindableRichTextBox Text="{Binding XamlText, Mode=TwoWay}"/>
In the viewmodel I've created the XamlText as a normal property with the NotifyPropertyChanged event being called on updates.
I want the bound XamlText to be updated when the user enters texts in the RichTextBox either on lostfocus or directly during edit, it doesn't really matter.
How can I change the code to make this happen?
You will need to listen to changes to the Xaml-property of the BindableRichTextBox and set the Text-property accordingly. There is an answer available here describing how that could be achieved. Using the approach described in that would the result in the following code (untested):
public BindableRichTextBox()
{
this.RegisterForNotification("Xaml", this, (d,e) => ((BindableRichTextBox)d).Text = e.NewValue);
}
public void RegisterForNotification(string propertyName, FrameworkElement element, PropertyChangedCallback callback)
{
var binding = new Binding(propertyName) { Source = element };
var property = DependencyProperty.RegisterAttached(
"ListenAttached" + propertyName,
typeof(object),
typeof(UserControl),
new PropertyMetadata(callback));
element.SetBinding(property, binding);
}
I have a TextBox on WPF that I want to validate. I'm using Binding to validate it:
<TextBox Text="{Binding Path=Name, UpdateSourceTrigger=Explicit}" TabIndex="0" LostFocus="TextBox_OnLostFocus">
</TextBox>
The LostFocus event:
private void TextBox_OnLostFocus(object sender, RoutedEventArgs e)
{
((Control) sender).GetBindingExpression(TextBox.TextProperty);
}
Code behind the validation:
public string Name
{
get { return _name; }
set
{
_name = value;
this.OnPropertyChanged(new PropertyChangedEventArgs("Name"));
}
}
public string Error { get { return this[null]; } }
public string this[string columnName]
{
get
{
string result = string.Empty;
columnName = columnName ?? string.Empty;
if (columnName == string.Empty || columnName == "Name")
{
if (string.IsNullOrEmpty(this.Name))
{
result += Properties.Resources.ValidationName + Environment.NewLine;
}
}
return result.TrimEnd();
}
}
I have some questions:
1. When I first load my Window, my control is surrounded by a red square (the validation one), but I want it to appear only when I fire it (on the Explicit side).
2. How can I know if all my fields have been validated? I mean, when I press a button I only need to know how to know if all controls have been validated.
NOTE: I do have this context on the Constructor:
User u = new User();
DataContext = u;
Your first question may be answered here Did you try setting the binding mode to Default?
The Validation.HasError Attached Property will tell you if any binding on a particular UI Element has any binding validation errors. Use that on every control you need to have validated. Try that first. If you are using a pattern like MVVM, you could create properties on your VM to bind to the Validation.HasError properties.
Actually, my problem had something to do with the class I used to validate. The class does this:
public ErrorProvider()
{
this.DataContextChanged += new DependencyPropertyChangedEventHandler(ErrorProvider_DataContextChanged);
this.Loaded += new RoutedEventHandler(ErrorProvider_Loaded);
}
So whenever it first loads, it suscribes to the Load event and then it launches this:
private void ErrorProvider_Loaded(object sender, RoutedEventArgs e)
{
Validate();
}
so I commented it, and launched the Validate() method when needed....
Not really sure how to tackle this issue:
I have a "Save" button that has access keys attached to it... but, if I type something into a textbox and press the access keys to save, the textbox doesn't update my viewmodel because it never lost focus. Any way to solve this outside of changing the UpdateSourceTrigger to PropertyChanged?
Your problem is the UpdateSourceTrigger="LostFocus"
This is default for TextBoxes, and means that the TextBox will only update its bound value when it loses focus
One way to force it to update without setting UpdateSourceTrigger="PropertyChanged" is to hook into the KeyPress event and if the key combination is something that would trigger a save, call UpdateSource() first
Here's an Attached Property I like using when the Enter key should update the source.
It is used like this:
<TextBox Text="{Binding Name}"
local:TextBoxProperties.EnterUpdatesTextSource="True" />
and the Attached Property definition looks like this:
public class TextBoxProperties
{
public static readonly DependencyProperty EnterUpdatesTextSourceProperty =
DependencyProperty.RegisterAttached("EnterUpdatesTextSource", typeof(bool), typeof(TextBoxProperties),
new PropertyMetadata(false, EnterUpdatesTextSourcePropertyChanged));
// Get
public static bool GetEnterUpdatesTextSource(DependencyObject obj)
{
return (bool)obj.GetValue(EnterUpdatesTextSourceProperty);
}
// Set
public static void SetEnterUpdatesTextSource(DependencyObject obj, bool value)
{
obj.SetValue(EnterUpdatesTextSourceProperty, value);
}
// Changed Event - Attach PreviewKeyDown handler
private static void EnterUpdatesTextSourcePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var sender = obj as UIElement;
if (obj != null)
{
if ((bool)e.NewValue)
{
sender.PreviewKeyDown += OnPreviewKeyDown_UpdateSourceIfEnter;
}
else
{
sender.PreviewKeyDown -= OnPreviewKeyDown_UpdateSourceIfEnter;
}
}
}
// If key being pressed is the Enter key, and EnterUpdatesTextSource is set to true, then update source for Text property
private static void OnPreviewKeyDown_UpdateSourceIfEnter(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
if (GetEnterUpdatesTextSource((DependencyObject)sender))
{
var obj = sender as UIElement;
BindingExpression textBinding = BindingOperations.GetBindingExpression(
obj, TextBox.TextProperty);
if (textBinding != null)
textBinding.UpdateSource();
}
}
}
}
I have a Windows Forms ListBox data-bound to a BindingList of business objects. The ListBox's displayed property is a string representing the name of the business object. I have a TextBox that is not data-bound to the name property but instead is populated when the ListBox's selected index changes, and the TextBox, upon validation, sets the business object's name property and then uses BindingList.ResetItem to notify the BindingList's bound control (the ListBox) to update itself when the TextBox's text value is changed by the user.
This works great unless the name change is only a change in case (i.e. "name" to "Name"), in which case the ListBox doesn't get updated (it still says "name", even though the value of the underlying business object's name property is "Name").
Can anyone explain why this is happening and what I should do instead? My current workaround is to use BindingList.ResetBindings, which could work for me but may not be acceptable for larger datasets.
Update 9/27/2011: Added a simple code example that reproduces the issue for me. This is using INotifyPropertyChanged and binding the textbox to the binding list. Based on How do I make a ListBox refresh its item text?
using System;
using System.ComponentModel;
using System.Windows.Forms;
namespace WinformsDataBindingListBoxTextBoxTest
{
public partial class Form1 : Form
{
private BindingList<Employee> _employees;
private ListBox lstEmployees;
private TextBox txtId;
private TextBox txtName;
private Button btnRemove;
public Form1()
{
InitializeComponent();
FlowLayoutPanel layout = new FlowLayoutPanel();
layout.Dock = DockStyle.Fill;
Controls.Add(layout);
lstEmployees = new ListBox();
layout.Controls.Add(lstEmployees);
txtId = new TextBox();
layout.Controls.Add(txtId);
txtName = new TextBox();
layout.Controls.Add(txtName);
btnRemove = new Button();
btnRemove.Click += btnRemove_Click;
btnRemove.Text = "Remove";
layout.Controls.Add(btnRemove);
Load += new EventHandler(Form1_Load);
}
private void Form1_Load(object sender, EventArgs e)
{
_employees = new BindingList<Employee>();
for (int i = 0; i < 10; i++)
{
_employees.Add(new Employee() { Id = i, Name = "Employee " + i.ToString() });
}
lstEmployees.DisplayMember = "Name";
lstEmployees.DataSource = _employees;
txtId.DataBindings.Add("Text", _employees, "Id");
txtName.DataBindings.Add("Text", _employees, "Name");
}
private void btnRemove_Click(object sender, EventArgs e)
{
Employee selectedEmployee = (Employee)lstEmployees.SelectedItem;
if (selectedEmployee != null)
{
_employees.Remove(selectedEmployee);
}
}
}
public class Employee : INotifyPropertyChanged
{
private string name;
private int id;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged("Name");
}
}
public int Id
{
get { return id; }
set
{
id = value;
OnPropertyChanged("Id");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
#endregion
}
}
Update 9/28/2011: The problem seems to be internal to the ListBox control, specifically the way it decides (not) to update an item if its string representation is equivalent to the new value, ignoring case differences. As far as I can tell this is hard coded into the control with no way to override it.
Think I found the problem:
I just hope this concept will be helpfull to solve your problem
I have a TextBox, the user input is compared to string "Name". My button click event is:
private void btn_Click(object sender, EventArgs e)
{
if(txt.Text == "Name")
MessageBox.Show("Value is Same");
}
If you write "name" in textbox, condition will be false. If you type type "Name" in textbox, condition will be true.
Now try changing the btn click:
using System.Globalization;
private void btn_Click(object sender, EventArgs e)
{
TextInfo ChangeCase = new CultureInfo("en-US", false).TextInfo;
string newText = ChangeCase.ToTitleCase(txt.Text);
if (newText == "Name")
MessageBox.Show("Value is Same");
}
now you type "name" or "Name" condition is true.
Remember it will just capaitalize the first letter of the string suplied. "my name" will outputted as "My Name".
And if your condition says:
if(txt.Text == "name")
MessageBox.Show(Value is Same);
Then you can try something like
string newText = (txt.Text).ToLower();
if(newText == "name")
MessageBox.Show(Value is Same);
Here the supplied string will outputted in the lower case always.
Hope it helps.
This really is the same problem as when renaming files or directories while only case is different. I suggest the same work-around that I found earlier:
if (oldValue.ToUpper() == newValue.ToUpper()){
ListBox1.Items[i] = newValue + "_tmp"; // only adding stuff to force an update
ListBox1.Items[i] = newValue; // now the new value is displayed, even only case has changed
}
Now for your question, I suggest you try to check if the setter is changing a value only in lower/upper case (a.ToUpper() == b.ToUpper()). If true, then first give a extra change, before the intended change, something like:
name = value + "_tmp";
OnPropertyChanged("Name");
name = value;
OnPropertyChanged("Name");
Hope this helps.
I am working on WPF project. I create a usercontrol containing a combobox; which represent boolean value(True or false). And I register a DependencyProperty Value for my usercontrol.
Whenever combobox selection was changed, I will update the Value property and also when Value property is update I will update combobox.
But I found the problem when I use my usercontrol in MVVM. I bind the Value property with my IsEnable property in my viewModel. I set binding mode as TwoWay binding. But when I changed selection in comboBox, IsEnable property is never set.
My usercontrol:
public bool Value
{
get { return (bool)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(bool),
typeof(BooleanComboBox),
new UIPropertyMetadata(true, OnValuePropertyChanged));
private void Cmb_Selection_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ComboBox cmb = sender as ComboBox;
object selectedValue = cmb.SelectedValue;
if (selectedValue == null)
{
this.Value = false;
}
else
{
if (selectedValue.GetType() == typeof(bool))
{
this.Value = (bool)selectedValue;
}
else
{
this.Value = false;
}
}
if (this.OnValueChange != null)
this.OnValueChange(this, this.Value);
}
private static void OnValuePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
BooleanComboBox self = sender as BooleanComboBox;
self.Cmb_Selection.SelectedValue = (bool)args.NewValue;
}
In window, where I place my usercontrol (I already set usercontrol's datacontext to my viewModel):
<tibsExtControl:BooleanComboBox Grid.Row="4"
Grid.Column="1"
VerticalAlignment="Center"
Value="{Binding Path=NewTemporaryZone.IsEnable,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
x:Name="Cmb_AllowNonLBILogon"/>
In my model class I declare an IsEnable property:
private bool _isEnable;
public bool IsEnable
{
get { return _isEnable; }
set
{
_isEnable= value;
OnPropertyChanged("IsEnable");
}
}
What's going on with my usercontrol. I miss something ? Please help me. T.T
Please check whether you have any binding error in the output window of VS.
Try refreshing your binding in Cmb_Selection_SelectionChanged. Something like:
BindingExpression b = cmb.GetBindingExpression(MyNamespace.ValueProperty);
b.UpdateSource();
I've had the same problem; with boolean dependency properties! Try switching the bool to a INullable<bool> (bool?) and apply the appropriate type conversions. This worked for me. Don't know if this is a bug or if value types are handled somewhat different compared to reference types when creating dependency properties? Maybe someone else could verify that.