I m using the binding in Silverlight. I have binded the TextBox with Decimal entity.
Below is the Code snipts of bindings.
<TextBox x:Name="AmountBox" Text="{Binding SelectedEntity.Amount,Mode=TwoWay,StringFormat=\{0:n2\},Converter={StaticResource DecimalBlankValueConverter}}" Validate="True" TextChanged="AmountBox_TextChanged" LostFocus="AmountBox_LostFocus"/>
Below is the converter code.
decimal result;
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (!Decimal.TryParse(value.ToString(),out result) || (decimal)value == decimal.Zero)
return null;
return decimal.Parse(value.ToString());
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null || !Decimal.TryParse(value.ToString(), out result))
return 0.00;
return decimal.Parse(value.ToString());
}
on lost focus I am updating the source
with
GetBindingExpression(TextBox.TextProperty).UpdateSource();
Everything is workig well, But convert is not called on lost focus and when I am entering string in the textbox, than convert is not called and it will not convert the textBox text to blank.
Can anyone please suggest me , what is the probelm in the code.
Thanks in advance.
----Raj
TwoWay mode binding with TextChanged and LostFocus events are definitely not the best approach. Why do you call GetBindingExpression(TextBox.TextProperty).UpdateSource(); in the LostFocus if binding does it by default? If you want to update a binding manually, set UpdateSourceTrigger=Explicit.
Anyway your converter seems fine, but I think you don't need it. If I understand right, you want to clear the text if it cannot be cast to decimal. In that case you have a few options, like creating your custom TextBox that allows only digits, or you can check NumericUpDown control from Silverlight Toolkit. Also I've found similar question here.
If you have installed Microsoft Expression Blend (if don't, you can download BlendSLSDK_en.msi from Microsoft Expression Blend Software Development Kit), add System.Windows.Interactivity dll to your project and create simple Behavior like this:
public class TextBoxClearTextBehavior : System.Windows.Interactivity.Behavior<System.Windows.Controls.TextBox>
{
protected override void OnAttached()
{
AssociatedObject.LostFocus += AssociatedObjectLostFocus;
base.OnAttached();
}
protected override void OnDetaching()
{
AssociatedObject.LostFocus -= AssociatedObjectLostFocus;
base.OnDetaching();
}
private void AssociatedObjectLostFocus(object sender, System.Windows.RoutedEventArgs e)
{
decimal result;
if (!decimal.TryParse(AssociatedObject.Text, out result))
AssociatedObject.Text = string.Empty;
}
}
and use it like
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
...
<TextBox Text="{Binding Amount, Mode=TwoWay, StringFormat=\{0:n2\}}">
<i:Interaction.Behaviors>
<Behavior:TextBoxClearTextBehavior/>
</i:Interaction.Behaviors>
</TextBox>
As per your description, "convert is not called on lost focus and when I am entering string in the textbox, than convert is not called and it will not convert the textBox text to blank."
Here, the Convert method will only be called when the binding with the Text property of TextBox changes. Since, you have mentioned Mode=TwoWay for binding, when you enter any text in textbox, the ConvertBack method will be called and the value returned from this method will be assigned to the Source, which, in your case, is SelectedEntity.Amount.
I do not understand why we need to explicitly write the GetBindingExpression(TextBox.TextProperty).UpdateSource(); code on lost focus to update the source.
Ideally, since you have kept the binding mode Two way, it should update the source after calling the ConvertBack method of Converter.
The converter code looks ok to me.
Let me know if you need more details or if there is something that I may have misunderstood, please clarify on those points.
If you update manually the binding in the TextChanged event, like this:
GetBindingExpression(TextBox.TextProperty).UpdateSource();
maybe the value you will get in the LostFocus event will not be different from which one you updated in the TextChanged event!
So, probably the converter will not be called, just because the value in the binding is not changed!
Hope this helps!
Related
I have a DataGrid with editable cells bound to their respective values in the view model of the respective items.
Initially, the data is loaded and displayed to the user, who can then edit the data in the grid.
Binding is working as it should (in my case with UpdateSourceTrigger=OnPropertyChanged), but due to conversions between double (view model) and string (UI), a TwoWay binding would cause annoying UI bugs like making decimal separators or zeros after a decimal point disappear when typed by the user.
Two faulty solutions are:
Making the property a string in the view model, and doing the necessary conversions inside the view model.
Problem: brings me a strange problem of incompatible cultures between the UI and the view model (and I don't expect the view model to know the UI's culture)
Using a OneWayToSource binding. This eliminates all UI bugs as the VM stops sending back parsed and reconverted values.
Problem: I can't (or don't know how to) initialize the values in the grid with the loaded data.
So, can I somehow use a OneWayToSource binding "after" a OneTime binding, or somehow sum the two?
I tried to bind FallbackValue and TargetNullValue to the source values, but they don't accept bindings.
The decimal place disappearing is a "feature" they introduced whilst trying to fix something else. I thought it was .Net 4.0 this was introduced and people started noticing it was a breaking change but the documentation seems to imply .Net 4.5.
It usually occurs because you set updatesourcetrigger=propertychanged.
The simple fix is often to just remove that.
Because
, UpdateSourceTrigger=LostFocus
Is the default behaviour for textbox text binding.
Alternatively, you could experiment with KeepTextBoxDisplaySynchronizedWithTextProperty
https://msdn.microsoft.com/en-us/library/system.windows.frameworkcompatibilitypreferences.keeptextboxdisplaysynchronizedwithtextproperty(v=vs.110).aspx
public MainWindow()
{
FrameworkCompatibilityPreferences.KeepTextBoxDisplaySynchronizedWithTextProperty = false;
InitializeComponent();
}
You can set that in Mainwindow before anything is displayed.
I found out a hacky workaround that involves using two properties, the original and a string dedicated to the user for smooth behavior. Use a specific converter for that purpose. (I think I'll adopt this as a pattern for future cases)
This works for cases where the view model does not change the property, only the user changes the property. (If you want a truly two way interaction where the view model also changes the property, you need to set the string property to null whenever you need to change the property)
In the view model:
The differences from a standard code are:
adding a string property without logic
adding a notification for this property when the original property changes
Code:
private double? _TheProperty;
public double? TheProperty { get { return _TheProperty ; } set { SetTheProperty (value); } }
public string ThePropertyUserString { get; set; } //for UI only!!! Don't change via code
private void SetTheProperty(double? value)
{
if (value == null)
{
//implement validation errors if necessary
//using IDataErrorInfo and ValidatesOnDataErrors
//this type of validation is the only I found that helps enabling/disabling command buttons
}
//do your logic
_TheProperty = value;
//notify
if (PropertyChanged != null)
{
PropertyChanged("TheProperty", ...);
PropertyChanged("ThePropertyUserString", ...);
}
}
In the XAML:
<TextBox Style="{StaticResource ErrorStyle}">
<TextBox.Text>
<MultiBinding UpdateSourceTrigger="PropertyChanged"
Mode="TwoWay"
Converter="{StaticResource DoubleUserStringConverter}">
<Binding Path="TheProperty" ValidatesOnDataErrors="True"/>
<Binding Path="ThePropertyUserString"/>
/MultiBinding>
</TextBox.Text>
</TextBox>
The converter:
/// <summary>
/// multibinding, first binding is double? and second is string, both representing the same value
/// the double? value is for the viewmodel to use as normally intended
/// the string value is for the user not to have ui bugs
/// </summary>
class DoubleUserStringConverter : IMultiValueConverter
{
private OriginalConverterYouWanted converter;
public DoubleUserStringConverter()
{
converter = new OriginalConverterYouWanted(); //single binding, not multi
//for types "double" in the view model and "string" in the UI
//in case of invalid strings, the double value sent to UI is null
}
//from view model to UI:
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[1] == null) //null string means UI initialization, use double
return converter.Convert(values[0], targetType, parameter, culture);
else
return values[1]; //in the rest of the time, send user string to UI
}
//from UI to view model
public object[] ConvertBack(object value, Type[] targetTypes,
object parameter, CultureInfo culture)
{
return new object[] {
converter.ConvertBack(value, targetTypes[0], parameter, culture), //can be null
value //string is always sent as is, no changes to what the user types
};
}
}
In our project we have a WPF textbox that is bound to a double. There is a converter that allows in the convertback to for example use both "." and "," to as decimal points and in the convert method it formats the double to the n2 numeric format.
Here you can see a simplefied version of our converter:
public class DoubleConverter:IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null || value.GetType() != typeof(double))
return null;
else
return ((double)value).ToString("n2");
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
return null
else
return Double.Parse(((string)value).Replace('.',','));
}
}
The textbox looks like this:
<TextBox Text="{Binding Path=Factor, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" Width="500" Height="50" />
And the double property raises a propertychanged event:
public double Factor
{
get { return _factor; }
set
{
_factor = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Factor"));
}
}
This used to work fine in previous versions of WPF because the convert was'nt called when you were entering text. This behavior apparently has changed and now the convert method is called on every text entry, causing the double to be formatted when you type something.Even if you don't use formatting you have the problem of not being able to enter a decimal point.
This can be solve by not using UpdateSourceTrigger=Propertychanged, but we need this for validation. We implement validation by using the IDataErrorInterface. I know there is a ValidateWithoutUpdate method, but this doesn't work for validation using the IDataErrorInterface.
So what I basicly need is ConvertBack (and thus the validation) to happen OnPropertyChanged and the Convert to only happen OnLostFocus.
Is this possible? Or is there another solution for our problem?
Yes, the behavior change from .Net 3.5 to .Net 4.0, where it now sends source updates back to targets even if the update originated from the target. This SO post explains it a bit and provides a solution:
Why does binding setup behave differently in .NET 4 vs .NET 3.5
These are the options I've found so far for dealing with this:
Have the converter maintain the state of the input on ConvertBack and restoring it in Convert (suggested in link above)
Change backing data to strings, handle conversion in model
Create a custom control that handles this kind of numeric input similar to how a DateTimePicker works (it maintains a string and double representation of the data; displays the string, but is bound by the double)
I'm pretty new to ObservableCollections, but have built some code which I'm sure should work. Unfortunately it doesn't. The only thing that is not happening, is my GUI is not being updated. I know the values are being updated in the back (Checked using Debugger).
What am I doing wrong?
Here with a sample of my XAML for the Textblock:
<TextBlock Name="tbCallsOpen" Text="{Binding IndicatorValue}" />
Herewith sample of my code behind:
public partial class CurrentCalls : UserControl
{
Microsoft.SharePoint.Client.ListItemCollection spListItems;
ObservableCollection<CurrentCallIndicator> CallIndicators = new ObservableCollection<CurrentCallIndicator>();
public CurrentCalls()
{
InitializeComponent();
DispatcherTimer dispatchTimer = new DispatcherTimer();
dispatchTimer.Interval = new TimeSpan(0, 0, 20);
dispatchTimer.Tick += new EventHandler(BindData);
dispatchTimer.Start();
}
private void BindData(object sender, EventArgs args)
{
//splistitems is a sharepoint list. Data is being retrieved succesfully, no issues here.
foreach (var item in spListItems)
{
//My custom class which implements INotifyPropertyChanged
CurrentCallIndicator indicator = new CurrentCallIndicator();
indicator.IndicatorValue = item["MyValueColumn"];
//Adding to ObservableCollection
CallIndicators.Add(indicator);
}
//Setting Datacontext of a normal TextBlock
tbCallsOpen.DataContext = CallIndicators.First(z => z.IndicatorName == "somevalue");
}
}
You are most likely assuming that changes to the underlying items in the collection will raise the CollectionChanged event; however that is not how the ObservableCollection<T> works.
If you wanted this behavior you would need to roll your own implmentation and when a PropertyChanged event is fired within an item within your collection, you would then need to fire the CollectionChanged event.
Your code looks more-or-less correct to me, at first blush - though I wouldn't expect that you'd need to use an ObservableCollection<> to get the results you seem to be expecting: a simple List<> would work just fine.
If the debugger tells you that the DataContext is being updated correctly to the expected item, then the most likely issue is that there's a problem with how your binding is defined. If you're not seeing any binding errors reported in your debug window, then I'd look into Bea Stollnitz' article on debugging bindings. Most specifically, I often use the technique she suggests of a "DebugValueConverter", e.g.:
/// <summary>
/// Helps to debug bindings. Use like this: Content="{Binding PropertyName, Converter={StaticResource debugConverter}}"
/// </summary>
public class DebugConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
And then set a breakpoint in your converter, and watch what's happening. It's a hack and a kludge, but until we're all on SL5 (which has binding debugging built in), it's your best bet.
Ok, Sorted. I fixed the issue myself. Because I was updating the values in a loop, the ObservableCollection wasn't being updated properly. All I did in the beginning of the databinding method, was to Clear the collection : CallIndicators.Clear();
Is there any way to change the value of property at runtime in WPF data binding. Let's say my TextBox is bind to a IsAdmin property. Is there anyway I can change that property value in XAML to be !IsAdmin.
I just want to negate the property so Valueconverter might be an overkill!
NOTE: Without using ValueConverter
You can use an IValueConverter.
[ValueConversion(typeof(bool), typeof(bool))]
public class InvertBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool original = (bool)value;
return !original;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
bool original = (bool)value;
return !original;
}
}
Then you'd setup your binding like:
<TextBlock Text="{Binding Path=IsAdmin, Converter={StaticResource boolConvert}}" />
Add a resource (usually in your UserControl/Window) like so:
<local:InvertBooleanConverter x:Key="boolConvert"/>
Edit in response to comment:
If you want to avoid a value converter for some reason (although I feel that it's the most appropriate place), you can do the conversion directly in your ViewModel. Just add a property like:
public bool IsRegularUser
{
get { return !this.IsAdmin; }
}
If you do this, however, make sure your IsAdmin property setter also raises a PropertyChanged event for "IsRegularUser" as well as "IsAdmin", so the UI updates accordingly.
If you specifically want to do this at XAML end (I am not sure the reason for that, unless you have 100s of similar operation of negate) there are only two ways 1) Using IValueConverter 2)write a XAML Markup Extension (Way too much work for this small task :))
Then the other obvious way is to write another property in your ViewModel , which can return the Negative of the IsAdmin property.
You can't bind to !Property, but you could create a new Binding with an appropriate IValueConverter and change out the entire Binding at runtime. The key is the BindingOperations class, which allows you to change the binding on a particular DependencyProperty.
public static void InvertBinding(DependencyObject target, DependencyProperty dp)
{
//We'll invert the existing binding, so need to find it
var binding = BindingOperations.GetBinding(target, dp);
if (binding != null)
{
if (binding.Converter != null)
throw new InvalidOperationException("This binding already has a converter and cannot be inverted");
binding.Converter = new InvertingValueConverter(); //This would be your custom converter
//Not sure if you need this step, but it will cause the binding to refresh
BindingOperations.SetBinding(target, dp, binding);
}
}
This should give you a general idea; I wouldn't use this for production code, as you'd probably want to generalize it to toggle the converter or whatever else you need to change out at runtime. You could also avoid changing the binding entirely by creating a new property you bind to that encapsulates this 'switching' logic. The last option is probably the best.
You can write a ValueConverter that automatically negates the input before returning it. Have a look at BenCon's blog for a short reading on value converters.
I'm in the midst of testing a user control I've built, and I'm encountering something that's inexplicable to me.
The control's an extension of the ComboBox that handles values of a specific custom type. It has a dependency property of that custom type that is the target property of a Binding.
I've got a trace statement in the setter, and I can see that the property is getting set. But it's not appearing in my user control.
Now, ordinarily I'd say, okay, I've got a bug in my user control. I probably do, though I'm baffled about it. But this question isn't about finding the bug in my control. Read on; here is where it gets weird.
I'm also using Bea Stollnitz's little value converter to help debug the Binding:
public class DebuggingConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value; // Add the breakpoint here!!
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException("This method should never be called");
}
}
The idea behind this is that I add this converter to my Binding and can set a breakpoint to see what value is being pushed out to the target. Okay, that works just fine. I can see that the value is being pushed out.
In fact, it works a little too fine. If the DebuggingConverter is attached to the Binding, the user control displays the value. If it's not, it doesn't.
How is that even possible? How could a value converter that does nothing affect the behavior of a bound control?
Edit:
Not that it's likely to help, but here's the XAML for the user control:
<a:CodeLookupBox
Grid.Column="1"
Grid.IsSharedSizeScope="True"
MinWidth="100"
Style="{Binding Style}">
<a:CodeLookupBox.CodeLookupTable>
<Binding Path="Codes" Mode="OneWay"/>
</a:CodeLookupBox.CodeLookupTable>
<a:CodeLookupBox.SelectedCode>
<Binding Path="Value" Mode="TwoWay" ValidatesOnDataErrors="True"/>
</a:CodeLookupBox.SelectedCode>
</a:CodeLookupBox>
Without the converter on the second binding, the control behaves as though I didn't set SelectedCode. Even though a trace statement in the OnSelectedCodePropertyChanged handler shows that e.Value does indeed contain the correct value. This happens irrespective of whether the converter's attached or not.
I've been trying to reverse-engineer this problem with a thought experiment: if you wanted to create a bound user control whose behavior changed if a no-op converter were attached to its binding, how would you do it? I don't know enough about binding to come up with an answer.
Well, the good news is, I know why SelectedCode isn't being set when I'm not using a value converter. The bad news is, I still have something of a mystery, but the problem's been pushed up the food chain a bit, and I have a workaround.
This control is essentially a strongly-typed combo box with a bunch of additional features that are made possible by the fact that it knows what kind of items are in it. The SelectedCode and CodeLookupTable properties are strongly typed, and they hide the underlying SelectedItem and ItemsSource properties, which aren't. (This, by the way, is why this is a user control and not a subclass of ComboBox; I don't want those properties to be visible because a lot of things can happen if they get set improperly, none of them good.)
Here's what's happening. This is my debugging output when the value converter is attached (the number is the hash code of the control, because I've got a bunch of them that all get drawn simultaneously when the program's initialized):
14626603: OnCodeLookupTablePropertyChanged
CodeLookupTable property set to Proceedings.Model.CodeLookupTable
box.MainComboBox.ItemsSource = MS.Internal.Data.EnumerableCollectionView
14626603: OnSelectedCodePropertyChanged:
SelectedCode property set to Unlicensed Driver [VC12500(A)]
box.MainComboBox.ItemsSource = MS.Internal.Data.EnumerableCollectionView
This is the expected behavior. The CodeLookupTable property is set, so setting SelectedCode to one of the items in that collection correctly sets SelectedItem on the underlying ComboBox.
But without the value converter, we get this:
16143157: OnSelectedCodePropertyChanged:
SelectedCode property set to Unlicensed Driver [VC12500(A)]
box.MainComboBox.ItemsSource =
16143157: OnCodeLookupTablePropertyChanged
CodeLookupTable property set to Proceedings.Model.CodeLookupTable
box.MainComboBox.ItemsSource = MS.Internal.Data.EnumerableCollectionView
Here, the SelectedCode property is being set before the CodeLookupTable property is. So when the method tries to set SelectedItem on the underlying ComboBox, nothing happens, because the ItemsSource is null.
And here is the root of the problem. I've foolishly assumed that the order that bindings update their target in is the same as the order they're declared in the XAML. (One of the reasons I've expressed the bindings as elements instead of attributes is because the order of elements in an XML document is deterministic and the order of attributes isn't. It's not like I didn't think about this.) This is apparently not the case.
I've also assumed, maybe a little less foolishly, that the order in which bindings update their target isn't dependent on whether or not they have attached value converters. Well, it is. I wonder what else it depends on.
Mercifully, I have a way to work around this. Since my CodeLookup object contains a reference to the CodeLookupTable, I can make the SelectedCode setter set the CodeLookupTable (and thus the ItemsSource) property first, if it hasn't already been set. That'll make this problem go away without having to stick a fake value converter on the binding and hope that the way bindings behave never changes.
Edit
Here's what the property declarations look like:
#region SelectedCode
public static readonly DependencyProperty SelectedCodeProperty = DependencyProperty.Register(
"SelectedCode", typeof(CodeLookup), typeof(CodeLookupBox),
new FrameworkPropertyMetadata(OnSelectedCodePropertyChanged));
private static void OnSelectedCodePropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
CodeLookupBox box = (CodeLookupBox)source;
CodeLookup code = e.NewValue as CodeLookup;
// this right here is the fix to the original problem:
if (box.CodeLookupTable == null && code != null)
{
box.CodeLookupTable = code.Table;
}
box.MainComboBox.SelectedItem = e.NewValue;
}
public CodeLookup SelectedCode
{
get { return GetValue(SelectedCodeProperty) as CodeLookup; }
set { SetValue(SelectedCodeProperty, value); }
}
#endregion
#region CodeLookupTable
public static readonly DependencyProperty CodeLookupTableProperty = DependencyProperty.Register(
"CodeLookupTable", typeof(CodeLookupTable), typeof(CodeLookupBox),
new FrameworkPropertyMetadata(OnCodeLookupTablePropertyChanged));
private static void OnCodeLookupTablePropertyChanged(DependencyObject source,
DependencyPropertyChangedEventArgs e)
{
CodeLookupBox box = (CodeLookupBox)source;
CodeLookupTable table = (CodeLookupTable)e.NewValue;
box.ViewSource = new CollectionViewSource { Source = table.Codes };
box.View = box.ViewSource.View;
box.MainComboBox.ItemsSource = box.View;
}
public CodeLookupTable CodeLookupTable
{
get { return GetValue(CodeLookupTableProperty) as CodeLookupTable; }
set { SetValue(CodeLookupTableProperty, value); }
}
#endregion
try to implement the ConvertBack function too, you are using two-way binding so the problem may be that the exception will be ignored by the "xaml" but when you step in debug mode you maybe stop the "send value back"-operation?
Hmm...very strange. I've seen similar behavior when dealing with MultiBindings with multiple converters, but not on a simple binding. Out of curiosity, how are you defining your DPs on the control? (including the callbacks, metadata options, etc)
Some stuff to try (after removing the converter hook):
Default the mode (ie, remove the TwoWay)
Remove the ValidatesOnDataErrors
Add an AffectsRender to the SelectedCode DP
What does setting SelectedCode do? Can you post the code for the property changed handler?
You've suggested that debugging shows the property is being set to the correct value, so the most obvious suggestion is that the code providing your intended behaviour is incorrect.