ConvertBack not called on XAML Binding - c#

I'm using a DataGrid to display some shop stock information. Each item can belong to one type of stock.
The relevant entity ('StockEntity') has properties such as:
'ItemId', 'ItemType', 'Grocery', 'Reading', 'Bathroom'.
A couple of example rows in this table would be:
27, 'Grocery', 'Apple', null, null, null
127, 'Reading', null, 'Reading lamp', null, null
I have no control over the database/entity structure.
The DataGrid column is a custom column, containing (amongst others), a TextBox. The DataGrid is bound to an ObservableCollection of StockEntity objects. I want to bind the value of the TextBox to the relevant property. For example, if 'ItemType' = 'Grocery', the TextBox displays the 'Grocery' property. If I change the value in the textbox, it should get written back to the 'Grocery' property.
Here's what I have so far:
XAML:
<TextBox Grid.Column="0" Padding="5" VerticalAlignment="Center" Width="155">
<TextBox.Text>
<Binding Path="."
Converter="{StaticResource StockDataToTextConverter}"
Mode="TwoWay"
UpdateSourceTrigger="LostFocus">
</Binding>
</TextBox.Text>
</TextBox>
The converter is simple:
private StockEntity stock;
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
this.stock = value as StockEntity;
string text="";
if(this.stock!=null){
text = StockModel.GetStockData(this.stock);
}
return text;
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
string info = value as string;
if(info!=null && this.stock!=null){
StockModel.SetStockData(ref this.stock, info);
}
return stock;
}
The StockModel.Get/SetStockData() methods simply use reflection to get/put the info back into the correct property. The Convert() method works fine: the TextBox displays the correct data. If I edit the value in the TextBox, it just reverts back to the old value. ConvertBack() isn't called.
I think the ConvertBack() method isn't getting called because of the Binding Path=".", but I can't think of another way around this.
I also don't know if I can 'save' the bound object in the converter the way I have. It's critical that the value of the TextBox gets written back to the same entity object, to preserve the database connection properties of the entity!
Many thanks.

What I really wanted was something like:
<Binding Path={Binding Path="ItemType"} />
This is impossible!
The solution was to wrap the StockEntity object in my own class, and expose a 'binding property', which decided on which stock property to get/set. Instead of having a collection of StockEntity objects, I now have an ObservableCollection of WrapperClass objects to bind the DataGrid to. The wrapper looks something like:
public class WrapperClass{
public WrapperClass(StockEntity se)
{
this._stock = se;
}
private StockEntity _stock;
public stock {
get { return _stock; }
set { _stock = value; }
}
public string BindingProperty {
get
{
// use reflection to return value
return StockModel.GetStockData(this._stock);
}
set
{
// use reflection to set value
StockModel.SetStockData(ref this._stock);
}
}
}
The XAML was then simply:
<Binding Path="StockWrapper.BindingProperty" />

Related

TextBlock with binding not updated on property change [duplicate]

This question already has an answer here:
Databindings don't seem to refresh
(1 answer)
Closed 5 years ago.
In my UWP app I have a TextBlock, which should display a (formatted) date, that is bound to a property in the view model:
<TextBlock Style="{StaticResource summaryTextStyleHighlight}" Margin="0,10,0,0"
Text="{x:Bind ViewModel.CurrentDisplayDay, Converter={StaticResource DateFormatConverter}, ConverterParameter=d, Mode=OneWay}"
Name="lblCurrentDate" />
The converter is "configured" in the XAML like this:
<local:DateFormatConverter x:Key="DateFormatConverter" />
And the converter class is as followed:
public class DateFormatConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value == null)
return null;
DateTime dt = DateTime.Parse(value.ToString());
if (parameter != null)
{
return dt.ToString((string)parameter, Utils.GetCurrentCultureInfo());
}
return dt.ToString("g", Utils.GetCurrentCultureInfo());
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
The ViewModel has a simple property for the DateTime value that is bound:
public DateTime CurrentDisplayDay
{
get;
private set;
}
But when I update the value in the ViewModel, the value won't get updated in the TextBlock on the (Main)Page.
I tried to move the property to the page, but that didn't help. If I refresh the page (navigate to it again), then the updated value is being displayed but I don't want to navigate to it, it should show the updated value through the binding.
What could be the issue?
#Patric You seem to be doing almost everything correctly, but you have forgotten about one step.
Is there any notification indicating that your property has been updated, when its value changes? You need to propagate a notification to the UI indicating that your ViewModel property has been altered, because otherwise even though the Text Dependency property is actively listening for any notification from the "source" (you have defined the binding as One-Way), you are simply not communicating anything to it.
Your ViewModel should implement the INotifyPropertyChanged Interface, which exposes the PropertyChanged event.
The property changed event will be responsible for communicating the update.
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
On your property setter, you simply have to invoke this method, which will consequently be responsible for invoking the PropertyChanged event, with the appropriate event data.
Edit:
In order to use the CallerMemberName Attribute (which allows you to get the name of the property which called the method) use the following namespace System.Runtime.CompilerServices

Write simple converter

I have this Class:
public class MyData
{
public static int Total Files;
public static int Total FilesFinished;
}
And I have simple Progress-Bar that calculate its Value this way:
double value = ((double)MyData.FilesFinished / MyData.Files) * 100;
And update my Label using simple Timer:
Label name="lblPercentage" />
lblPercentage.Content = value;
Now I want to use Converter instead of updating my Label via code behind.
So I have this class (not implemented yet):
public class TotalFilesToTotalPercentageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Inside my Window.Resource I have this:
<Convertors:TotalFilesToTotalPercentageConverter x:Key="FilesToPercentageConverter "/>
And this is what I have try inside my Label:
Content="{Binding Converter={StaticResource FilesToPercentageConverter}}"
So my problem is that I try to see if my TotalFilesToTotalPercentageConverter class is responding via the debugger and it seems not, nothing happening.
What did I do wrong?
Update
I forget to mention that my TotalFilesToTotalPercentageConverter class in inside Converter folder under Utils folder under Classes folder
You need to bind the Content property to a source property for your Convert method to be invoked. Converters only work with data bindings.
This means that instead of setting the Content property of the Label in the code-behind like this:
lblPercentage.Content = value;
You should set a source property of a view model that you then bind the Content property of the Label to:
Content="{Binding Path=YourValueProperty, Converter={StaticResource FilesToPercentageConverter}}"
Set the DataContext of your view to an instance of your view model class:
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
The view model class needs to implement the INotifyPropertyChanged and raise the PropertyChanged event in the setter of the source property (YourValueProperty).

UWP TextBox not respecting TwoWay binding when typing

<!-- View -->
<TextBox Text="{Binding str, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
// View Model
private string _str;
public string str
{
get { return _str; }
set
{
if (!value.Contains("a"))
_str = value;
OnPropertyChanged(nameof(str));
}
}
When typing in the TextBox I want it to throw out any invalid characters (in this sample case the letter 'a', but it could really be for anything). For example:
User types 'fds' followed by an 'a'
str detects a, so it doesn't set _str to 'fdsa', keeping it at 'fds' but raises the event anyway to indicate to the view to throw out the 'a'.
In WPF, this results in the textbox containing 'fds'. In UWP, this results in the textbox incorrectly containing 'fdsa' still.
It appears that in UWP when a control has focus, it will not respect the TwoWay binding.
I can create a button that has a Click event that when pressed will update my TextBox correctly.
private void btn_Click(object sender, RoutedEventArgs e)
{
OnPropertyChanged(nameof(str));
}
We have many ViewModels that we need to use in both WPF and UWP views, and we have this required behavior all over the place. What is a good solution to this problem?
* EDIT *
Came back to the problem after the weekend and it seems to have fixed itself. I have no idea why. I am closing the question for now.
You could use a converter to solve your problem, you could elaborate a better converter, in my example I just use a silly converter to demonstrate my idea.
Converter:
public class Converter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null)
{
var someString = value.ToString();
return someString.Replace("a", "");
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
}
XAML
<TextBox Text="{Binding Str, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource converter}}"/>
You could use an attached behavior also.

Binding a list of elements to a collection-based dependency property in WPF

I'm implementing a custom Blend behavior in a WPF application (.Net 4.5). I've added a couple of dependency properties of type FrameworkElement to the behavior class to allow the user of the behavior to bind the elements of the view that they want to control. (This behavior invokes some animation on multiple elements, so I can't just use the AssociatedObject). This works fine and basically looks like this:
public class MyBehavior : Behavior<FrameworkElement>
{
public static readonly DependencyProperty Element1Property = DependencyProperty.Register("Element1", typeof (FrameworkElement), typeof (MyBehavior), new UIPropertyMetadata());
public FrameworkElement Element1
{
get { return (FrameworkElement) GetValue(Element1Property); }
set { SetValue(Element1Property, value); }
}
public static readonly DependencyProperty Element2Property = DependencyProperty.Register("Element2", typeof(FrameworkElement), typeof(MyBehavior), new UIPropertyMetadata());
public FrameworkElement Element2
{
get { return (FrameworkElement) GetValue(Element2Property); }
set { SetValue(Element2Property, value); }
}
}
Standard dependency property stuff. And I can use that in my view like this:
<Grid x:Name="Container">
<i:Interaction:Behaviors>
<local:MyBehavior
Element1="{Binding ElementName=FirstElement}"
Element2="{Binding ElementName=SecondElement}"
/>
</i:Interaction:Behaviors>
</Grid>
This works great and I can work with the elements in the behavior. But now I have a requirement to bind a list of elements like this. So I don't know ahead of time that there are going to be exactly 2 elements, there could be N elements that I need to work with. So I've added another property to the MyBehavior class like this:
public static readonly DependencyProperty ElementsProperty = DependencyProperty.Register("Elements", typeof(List<FrameworkElement>), typeof(MyBehavior), new UIPropertyMetadata(new List<FrameworkElement>()));
public List<FrameworkElement> Elements
{
get { return (List<FrameworkElement>) GetValue(ElementsProperty); }
set { SetValue(ElementsProperty, value); }
}
(And I've followed the advice here to initialize the list in the behavior's constructor.) But I can't figure out how to bind the list of elements to this property from the view's XAML. Basically, I want to do something along these lines:
<Grid x:Name="Container">
<i:Interaction:Behaviors>
<local:MyBehavior>
<local:MyBehavior.Elements>
<Binding ElementName="FirstElement" />
<Binding ElementName="SecondElement" />
<Binding ElementName="ThirdElement" />
</local:MyBehavior.Elements>
</local:MyBehavior>
</i:Interaction:Behaviors>
</Grid>
But of course this doesn't actually work. I've tried MultiBinding here, but that doesn't work either. Any idea what the XAML syntax would be for doing this, or is it even possible? If it's not possible, any ideas for other approaches to achieve this effect? Thanks!
I ended up solving this myself. It turns out, I could use a MultiBinding for this. The converter looks like this:
public class MultiFrameworkElementConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values != null ?
values.Cast<FrameworkElement>().ToList() :
new List<FrameworkElement>();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I should be a little more thorough in that converter and ensure that all of the objects in the values[] array are of type FrameworkElement, but this gets the idea across. Then in the XAML I can bind to the property on my behavior like this:
<local:MyBehavior.Elements>
<MultiBinding Converter="{StaticResource MultiFrameworkElementConverter}" Mode="OneTime">
<Binding ElementName="FirstElement" />
<Binding ElementName="SecondElement" />
<Binding ElementName="ThirdElement" />
</MultiBinding>
</local:MyBehavior.Elements>
I'm using the "OneTime" mode on the binding simply because these are UI elements in the view that I'm binding to the behavior. They will never change during the lifetime of the view and behavior. So no need to ever update the binding.
Overall, I'm satisfied with this. I can now allow the behavior to affect an arbitrary list of UI elements regardless of which view I use it on. I hope this description is able to help someone else trying to do something similar.

How to set a null value to a property when it throws an error?

Binding is so powerful in WPF. Supposed that we have a Number property (nullable int) and is bound to a textbox.
I realized when it throws an error, the property has the last correct value.
I mean these are the processes:
TEXTBOX: "" PROPERTY: null
TEXTBOX: "2" PROPERTY: 2
TEXTBOX: "2b" PROPERTY: 2 <-- here is the problem, should be null instead 2(by the error)
Is there a way which the binding set a null value when it produce an error?
Some persons told me I need to implement IDataErrorInfo, but I guess that interface is to validate business rules. So I wouldn't prefer user it.
UPDATE:
<TextBox Text="{Binding Number, UpdateSourceTrigger=PropertyChanged,
ValidatesOnExceptions=True, ValidatesOnDataErrors=True,
NotifyOnValidationError=True, TargetNullValue={x:Static sys:String.Empty}}"
You are using UpdateSourceTrigger=PropertyChanged, which means that anytime the user hits a key, it is storing the data in your data context
For example, user types 2, then your property is equal to "2". User types b and it will attempt to replace "2" with "2b", which fails, so the original property of "2" remains.
Remove the UpdateSourceTrigger and it will revert to the default of LostFocus, which means it will only update the property when the TextBox loses focus.
You could set the property to null when an error is produced, but I would not recommend doing that because then if a user accidently hits the wrong key, the TextBox would get cleared.
As a side note, use IDataErrorInfo for all validation, not just business rule validation. WPF is built to work with it. My Models use it to verify their data is the correct length, type, etc, and my ViewModels use it to verify that business rules are being followed
Edit
The alternative suggestion I would have would be to bind to a string value, not a number field. This way when the value changes, you can try and cast it to your Int and return an error if it can't be cast.
public class SomeObject : IDataErrorInfo
{
public string SomeString { get; set; }
public Int32? SomeNumber { get; set; }
#region IDataErrorInfo Members
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string columnName]
{
get
{
if (columnName == "SomeString")
{
int i;
if (int.TryParse(SomeString, i))
{
SomeNumber = i;
}
else
{
SomeNumber = null;
return "Value is not a valid number";
}
}
return null;
}
}
#endregion
}
I think that the simplest way to get that behaviour is to use an IValueConverter to convert from string to int?:
public class NullableIntConverter : IValueConverter
{
public static NullableIntConverter Instance = new NullableIntConverter();
public void ConvertBack(object value, ...)
{
int intValue = 0;
if (int.TryParse((string)value, out intValue))
return intValue;
return null;
}
public void Convert(object value, ...)
{
return value.ToString();
}
}
Then you can specify this in your binding as below (where local is mapped to your converter namespace):
<TextBox Text="{Binding Number, Converter="{x:Static local:NullableIntConverter.Instance}" ... />
It gets more powerful yet. You probably ought to go the route of validation via the interface/binding itself - WPF has built-in support for this, examples of which can be found in the Data Binding Overview over at MSDN.
An example of implementing this could go as follows:
<...>
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</...>
The linked documentation covers quite a bit on the topic of binding, so here is an excerpt from the relevant section 'Data Validation':
A ValidationRule object checks whether the value of a property is
valid.
A ExceptionValidationRule checks for exceptions thrown during the
update of the binding source property. In the previous example,
StartPrice is of type integer. When the user enters a value that
cannot be converted to an integer, an exception is thrown, causing the
binding to be marked as invalid. An alternative syntax to setting the
ExceptionValidationRule explicitly is to set the
ValidatesOnExceptions property to true on your Binding or
MultiBinding object.

Categories

Resources