Binding OneWayToSource with OneTime initalization of target - wpf

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

Related

Wpf textbox converter to double in combination with updatesourcetrigger=propertychanged

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)

Decimal Converter in Silverlight is not working on Lost Focus properly

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!

Silverlight UI not updating - ObservableCollection being reinstantiated

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();

Pass view to viewmodel with datatemplate

I have a window named ParameterEditorView with a ParameterEditorViewModel as DataContext. In the ParameterEditorViewModel I have a list of ParameterViewModel. In the ParameterEditorView I have an ItemsControl whose ItemsSource is binded to the list of ParameterViewModel in the ParameterEditorViewModel. I need the ParameterViewModel to have a reference to the ParameterView (more on that later). In the Resources section of the ParameterEditorView I add the DataTemplate:
<DataTemplate DataType="{x:Type my:ParameterViewModel}" >
<my:ParameterView HorizontalAlignment="Left"/>
</DataTemplate>
So, how can I pass a reference of the ParameterView that is created to show the ParameterViewModel to it?
The reason I need the ParameterView in the ParameterViewModel is the following:
I have a TextBox whose Text property is binded to the PropertyModelView.Name property. But I want to display a default string when the Name is empty or Null. I've tried to set the property value to the default string I want when that happens but the TextBox.Text is not set in this scenario. I do something like this:
private string _name;
public string Name
{
get { return _name; }
set
{
if (value == null || value.Length == 0)
Name = _defaultName;
else
_name = value;
}
}
I've also tried to specifically set the TextBox.Text binding mode to TwoWay without success.
I think this is a defense mechanism to prevent an infinite loop from happening but I don't know for sure.
Any help on this front would also be highly appreciated.
Thanks,
José Tavares
{Binding } has a FallbackValue, btw.
Your question, it confuses me. I'd assume your PVM has a collection of PV's as a public property, which is bound within the UI. Also, I think you're mixing terms. Its Model-View-ViewModel where the ViewModel is the DataContext of the View, and the Model is exposed by the ViewModel via a public property. Sounds like if you're binding the window to a collection of ViewModels they are actually Models. It may seem pedantic, but getting your terms correct will help you research and ask questions.
Another solution would be to add a Converter to your Binding in combination with FallbackValue (I've had to do this, IIRC). That converter would be an IValueConverter that returns "DependencyProperty.UnsetValue" if the string is null or empty. I think this works sometimes because the TextBox will set the bound property to the empty string rather than null if the TB is empty. Here's a little sample to whet your whistle (not guaranteed to work; you need to debug this and tweak it):
public class ThisMightWorkConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
var temp = value as string;
if(string.IsNullOrWhiteSpace(temp))
return System.Windows.DependencyProperty.UnsetValue;
return temp;
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
return value; // you might need to change this
}
}

WPF Property Data binding to negate the property

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.

Resources