<!-- 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.
Related
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
I am trying to enable a button only if text is present in two text boxes in a WinForm Application.
My Question is -
Can I achieve this using Data Binding?
If so how?
Edit
Please give reasons for downvote.
Update: Since OP want's to work with DataBinding only here is a solution with the desired technique.
You'll need to use a MultiBinding. Add the two Binding instances (one for each TextBox). Then you'll need to implement an IMultiValueConverter that will accept the values produced by the two binding objects and convert them into a single value.
The binding setup would look something like this:
var multiBinding = new MultiBinding();
multiBinding.Bindings.Add(new Binding("Enabled", textBox1, "Text", true));
multiBinding.Bindings.Add(new Binding("Enabled", textBox2, "Text", true));
multiBinding.Converter = new MyMultiValueConverter();
button1.DataBindings.Add(multiBinding);
And the converter implementation would look something like:
public class MyMultiValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
// perform your conversion here and return the final value
// so which value both textBoxes need to have that you return `true` so
// that `button1.Enabled` gets set to `true`
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
That you can use those classes MultiBinding and IMultiValueConverter you have to add a reference of the PresentationFramework lib to your project. Furthermore I suggest you to add:
using System.Windows.Data;
To shorten your code.
Since I already posted an answer and it's a legit working solution in the way OP desired it I don't edit the question, but rather show in a new question another approach.
You could create a computed property and bind the button.1.Enabled property to it. For example, create a textBoxesCorrect property that returns the value, and bind button.1.Enabled to it. The textBoxesCorrect property gets set in the TextChanged() events of those TextBoxes.
private void textBox1_TextChanged(object sender, EventArgs e)
{
if (textBox1.Text == "") //desired text that the textBoxes shell contain
MyData.textBox1Correct = true;
else
MyData.textBox1Correct = false;
}
private void textBox2_TextChanged(object sender, EventArgs e)
{
if (textBox2.Text == "") //desired text that the textBoxes shell contain
MyData.textBox2Correct = true;
else
MyData.textBox2Correct = false;
}
public class MyData
{
public static bool textBox1Correct { get; set; }
public static bool textBox2Correct { get; set; }
public bool textBoxesCorrect
{
get
{
if (textBox1Correct && textBox2Correct)
return true;
else
return false;
}
}
}
So can still work with your DataBinding, but it's an easier solution to implement to work with multiple sources for the binding.
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).
I have a custom textbox that has a dependencyProperty called valueProperty of type double nullable. My problem is that the property is binded to doubles nullables and no nullables on the model, and when I try to put a null and the binding value is no nullable, this obviously fails, showing the red rectangle. I want to detect the binding fail and assign a 0 when this occurs. So my question is: is there any way to detect that binding fail?
I know that I can fix it using 2 diferent customsTextbox for nullables and no nullables, and other ways, just wondering if there is a way to check the success of the binding. Thanks in advance.
EDIT >>>>>
Model:
private double _Temperature;
public double Temperature
{
get { return _Temperature; }
set { SetProperty(ref this._Temperature, value); }
}
private double? _Density;
public double? Density
{
get { return _Density; }
set { SetProperty(ref this._Density, value); }
}
View(simplified):
<local:customTextBox Value="{Binding Temperature}"/>
<local:customTextBox Value="{Binding Density}"/>
customTextBox dependencyProperty:
public static readonly DependencyProperty valueProperty =
DependencyProperty.RegisterAttached(
"Value",
typeof(double?),
typeof(customTextBox),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnValuePropertyChanged)
);
private static void OnValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
customTextBox ctb = d as customTextBox;
ntb.Value = (double?)e.NewValue;
//Here I can check the binding fail.
}
EDIT WITH SOLUTION >>>>>
There was diferent solutions to my problem, I'll enumerate them:
#blindmeis solution. This is the easiest one, but the less potent to:
<local:customTextBox Value="{Binding Temperature, TargeNullValue=0}"/>
#Gary H solution. This is the one I selected as the solution, because it answer exactly what I was asking and also the easier to implement in my current app:
private static void OnValuePropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
customTextBox ctb = d as customTextBox;
ntb.Value = (double?)e.NewValue;
if (Validation.GetHasError(d))
{
//The binding have failed
}
}
#tomab solution. I think using converters is a good solution to(maybe better), but I still need keeping the customTextBox class because of other dependency properties, and I will need to refactor so much code. I will keep in mind that way for future implementations.
Thank you all for the help.
Yes this is a validation error.
You can query for the attached property:
var errors=Validation.GetErrors(myTextbox);
For handling and customizing validation see:
Data validation in WPF
Here is a way to use a Converter and its advantage is that you can control how you want the output value (which will be displayed) based on any logic you need.
public class DoubleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
// if you want to handle `double` too you can uncomment following lines but this is an ugly hack
// if (value != null && value.GetType() == typeof(double))
// {
// return value;
// }
var nullableDouble = (double?)value;
if (nullableDouble.HasValue)
{
return nullableDouble.Value;
}
return 0;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
And the xaml may look like this:
<UserControl.Resources>
<converters:DoubleConverter x:Key="DoubleConverter"/>
</UserControl.Resources>
<TextBox Text="{Binding SomeValue, Converter={StaticResource DoubleConverter}}" />
SomeValue must be of type double?.
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" />