wpf c# databinding on object - c#

say I have this control:
public partial class bloc999 : UserControl
{
bloc999Data mainBlock = new bloc999Data();
public bloc999()
{
InitializeComponent();
mainBlock.txtContents = "100";
base.DataContext = mainBlock;
}
}
in the xaml:
<TextBox Margin="74,116,106,0" Name="txtContents"
Text="{Binding Path=txtContents, UpdateSourceTrigger=PropertyChanged,Mode = TwoWay}" />
<TextBox Margin="74,145,106,132" Name="txtContents2"
Text="{Binding Path=txtContents2, UpdateSourceTrigger=PropertyChanged,Mode = TwoWay}" />
Then I have this class:
public class bloc999Data : INotifyPropertyChanged
{
string _txtContents;
string _txtContents2;
public event PropertyChangedEventHandler PropertyChanged;
void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(
this, new PropertyChangedEventArgs(propName));
}
public string txtContents2
{
get
{
return this._txtContents2;
}
set
{
if (int.Parse(value) > int.Parse(this._txtContents))
{
this._txtContents2 = "000";
}
else
this._txtContents2 = value;
NotifyPropertyChanged("txtContents2");
}
}
public string txtContents
{
get
{
return this._txtContents;
}
set
{
this._txtContents = value;
NotifyPropertyChanged("txtContents");
}
}
}
Ok now say I have A button on the form and I do this in the code:
mainBlock.txtContents2 = "7777777";
It puts 000 in the textbox, but If i just type in manually, in the textbox (txtContents2), the setter code is called but for some reason the textboxes value does not change, the instance value does change. help?

I believe it's just because the value is changing within the context of the data binding operation, so WPF just ignores it because it knows the value is changing and thinks the event is superfluous. What it doesn't know is that you've gone and changed the value from the value WPF has to something else again.
If you do the notification in a separate message then WPF will process it outside the context of the current data binding operation and will thus pick up the change:
if (int.Parse(value) > int.Parse(this._txtContents))
{
this._txtContents2 = "000";
// notify WPF of our change to the property in a separate message
Dispatcher.BeginInvoke((ThreadStart)delegate
{
NotifyPropertyChanged("txtContents2");
});
}
else
{
this._txtContents2 = value;
NotifyPropertyChanged("txtContents2");
}
This assumes your view model has access to the Dispatcher. An example of how to do so is shown in my blog post on a base ViewModel class.

I was having similar problem earlier here
In your usercontrol, update Binding and set UpdateSourceTrigger to Explicit
<TextBox Margin="74,145,106,132" x:Name="txtContents2" TextChanged="txtContents2_TextChanged"
Text="{Binding Path=txtContents2, UpdateSourceTrigger=Explicit,Mode = TwoWay}" />
then in the TextChanged event handler update the binding manually by validating the input.
move validation logic from property txtContent2's setter in bloc999Data in this event handler
private void txtContents2_TextChanged(object sender, TextChangedEventArgs e)
{
if (int.Parse(txtContents2.Text) > int.Parse(mainBlock.txtContents))
{
mainBlock.txtContents2 = "000";
txtContents2.GetBindingExpression(TextBox.TextProperty).UpdateTarget();
}
else
{
mainBlock.txtContents2 = txtContents2.Text;
txtContents2.GetBindingExpression(TextBox.TextProperty).UpdateSource();
}
}
and it works.
Hope it helps!!

Related

Implement IDataErrorInfo on button click event

I'm trying to achieve validation on button click for a textbox using binding. Basically when I click Submit my textbox is not turning red and giving me the "Required" error, it is when I add text to it.
I'm new at validation and been looking at this for almost a week on and off in frustration. I think my answer may have something to-do with propertychangedevent? but I'm not sure and resorting to asking the professionals.
All and any help with this will be must appreciated.
Here is my Model class:
public class sForms : INotifyPropertyChanged, IDataErrorInfo
{
private string name;
public string NAME { get { return name; } set { if (name != value) name = value.Trim(); OnPropertyChanged("NAME"); } }
public string this[string columnName]
{
get
{
return ValidationError(columnName);
}
}
public string Error { get { return null; } }
private string ValidationError(string columnName)
{
string error = null;
switch (columnName)
{
case "NAME":
error = IsNameValid();
break;
}
return
error;
}
static readonly string[] ValidatedProperties = { "NAME" };
public bool IsValid
{
get
{
foreach (string property in ValidatedProperties)
{
if (ValidationError(property) != null)
{
return
false;
}
}
return
true;
}
}
public string IsNameValid()
{
if (string.IsNullOrWhiteSpace(NAME) || string.IsNullOrEmpty(NAME))
return "Required";
else
return
null;
}
#region Property Changed
private void OnPropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
Here is my XAML for my button + Text Box;
<TextBox Controls:TextBoxHelper.UseFloatingWatermark="True"
Controls:TextBoxHelper.Watermark="Name *"
Grid.Column="1" Grid.Row="1"
Margin="0 0 2 0"
Text="{Binding Path=NAME, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"
>
<Button Content="Submit"
Style="{DynamicResource SquareButtonStyle}"
VerticalAlignment="Bottom" HorizontalAlignment="Right"
Margin="0 0 10 0"
Click="Submit_Click"
/>
Here is my code behind;
public v_subsForm()
{
InitializeComponent();
this.DataContext = subs;
}
sForms subs = new sForms();
#region PropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
private void Submit_Click(object sender, RoutedEventArgs e)
{
if (subs.IsValid)
MessageBox.Show("True");
else
MessageBox.Show("False");
}
First, your code works as it should, assuming you included all MahApps.Metro resources that are required. Also, you don't need to implement INotifyPropertyChanged in your code-behind (that's your MainWindow I guess).
I'm trying to achieve validation on button click for a textbox using binding.
That is not how IDataErrorInfo works. IDataErrorInfo defines an API that the binding can query for errors on the object that it's bound to. So, when your NAME property is changed, the binding will query the indexer on your sForms object: subs["NAME"]. If it gets an error, error template is applied. This is usually paired with a submit button whose Command property is bound to a command whose CanExecute checks for errors and if there are errors the button is disabled (so you can not submit if there are errors, button is disabled).
If you want to do your validation on button click, you don't need to implement IDataErrorInfo. System.Windows.Controls.Validation class has attached properties that drive presentation of errors: HasError, Errors, ErrorTemplate. But you can't just set Validation.HasError to true (there is no accessible setter) like you can set Validation.ErrorTemplate. To set Validation.HasError in code-behind, you can use Validation.MarkInvalid method, but this is not how these things are usually done. Here is a quick example, for this to work you need to set Name property on your TextBox to MyTextBox:
private void Submit_Click(object sender, RoutedEventArgs e)
{
if (!string.IsNullOrEmpty(MyTextBox.Text)) return;
BindingExpression bindingExpression =
BindingOperations.GetBindingExpression(MyTextBox, TextBox.TextProperty);
BindingExpressionBase bindingExpressionBase =
BindingOperations.GetBindingExpressionBase(MyTextBox, TextBox.TextProperty);
ValidationError validationError =
new ValidationError(new ExceptionValidationRule(), bindingExpression);
validationError.ErrorContent = "My error message.";
Validation.MarkInvalid(bindingExpressionBase, validationError);
}
So if MyTextBox.Text is empty, it will be considered invalid.

Passing a string in a C# function that is used by WPF bounded control

How can I pass a string into a method?
I tried
private void UpdateValueRanges( .... , ref string decimalValueRange)
{
...
decimalValueRange = "10..20";
But I get the warning the the argument is overwritten without being used.
What I want to pass is the string that is bound to a text box control (via WPF, MVVM) and since I have multiple text box controls I want to pass the string belonging to the bound text box ... or can I only do this by passing the text box control itself?
I do not know if I understand your problem but if you want simply change control's property sending it as parameter with 'ref' maybe you should try this:
<!-- Window1.xaml -->
...
<Grid>
<TextBox Text={Binding MySampleText} />
</Grid>
...
I made simple binding from CodeBehind, first implement
/* Window1.xaml.cs */
public partial class Window1 : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
...
}
Next full property with OnPropertyChanged trigger and handler:
private string _mySampleText;
public string MySampleText
{
get { return _mySampleText; }
set
{
if (value != _mySampleText)
{
_mySampleText = value;
OnPropertyChanged("MySampleText");
}
}
}
...
protected void OnPropertyChanged(string passedValue)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(passedValue));
}
Finally constructor with method call:
public Window1()
{
InitializeComponent();
DataContext = this;
UpdateValueRanges(ref _mySampleText);
}
private void UpdateValueRanges(ref string decimalValueRange)
{
decimalValueRange = "10..20";
}
If u have alert that "...the argument is overwritten without being used", use 'out' instead of 'ref' -
When to use ref vs out
public Window1()
{
InitializeComponent();
DataContext = this;
UpdateValueRanges(out _mySampleText);
}
private void UpdateValueRanges(out string decimalValueRange)
{
decimalValueRange = "10..20";
}
Here is similar problem maybe that I found - Pass by Ref Textbox.Text

WPF - Programmatically created controls validation in ItemsControl

I'm struggling with weird issue in my WPF code. I have a combobox, which allows user to choose one of its options. Each item in this combobox is some kind of string.Format() pattern. For example when user chooses option 'Hello {0} world', my code generates two TextBlocks with 'Hello' and 'world' and one TextBox between them, where user can provide his input.
Here's my xaml:
<ComboBox ItemsSource="{Binding PossiblePatternOptions}" DisplayMemberPath="Pattern" SelectedItem="{Binding SelectedPattern, ValidatesOnDataErrors=True}" Width="250" Margin="5,0,25,0"/>
<ItemsControl ItemsSource="{Binding SelectedPattern.GeneratedControls}"/>
SelectedPattern.GeneratedControls is an ObservableCollection<UIElement>:
public ObservableCollection<UIElement> GeneratedControls
{
get
{
return _generatedControls ?? (_generateControls = new ObservableCollection<UIElement>(GenerateControls(_splittedPattern)));
}
}
Here's how I create new TextBox (in GenerateControls method):
var placeholderChunk = chunk as TextBoxPlaceholder;
var textBox = new TextBox();
textBox.ToolTip = placeholderChunk.Description;
Binding binding = new Binding("Value");
binding.ValidatesOnDataErrors = true;
binding.Source = placeholderChunk;
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
textBox.SetBinding(TextBox.TextProperty, binding);
TextBoxPlaceholder implements IDataErrorInfo and provides error details for incorrect input:
public class TextBoxPlaceholder : IDataErrorInfo
{
public string Value {get;set;}
public string this[string columnName]
{
get
{
switch (columnName)
{
case "Value":
return string.IsNullOrEmpty(Value) ? "Error" : string.Empty;
default:
return string.Empty;
}
}
}
public string Error
{
get { throw new NotImplementedException(); }
}
}
The problem is that when I choose an option from combobox for the first time, generated TextBoxes are validated correctly and they get nice red frame around them, but when I choose an option that has been chosen previously, no validation occurs and there is no red frame anymore. I noticed that when I change code in GeneratedControls property so it recreates collection every time, it works ok. What could possibly be the problem here?
I know it may be explained poorly, in case of any misunderstandings I will clarify.
It seems your "Value" property does not fire an update when it changes, so the text binding of the text box cannot react on the change and will not evaluate the value again. Try something like this:
// implmeent INotifyPropertyChanged
public class TextBoxPlaceholder : IDataErrorInfo, System.ComponentModel.INotifyPropertyChanged
{
private string mValue;
public string Value
{
get{ return mValue; }
// fire notification
set{mValue = value;NotifyPropertyChanged("Value");}
}
public event PropertyChangedEventHandler PropertyChanged;
// helper method
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
// your code goes here

Change enabled property of a WPF control (button) based on textbox content

Im looking for a solution in WPF to change the IsEnabled property of a button based on the content of a textbox. The TextBox holds a numeric value. If the value is greater than a certain value the IsEnabled property of the button should be set to true, as long as it is below this value the property should be false.
I have been looking around but couldn't find a proper solution. What i found here on CodeProject is almost what im looking for. But the problem is that this approach just checks if any content is in the textbox. But i need to check/compare the numeric content.
I would prefer to find a way to do it in XAML. Alternatively i could implement it also in my ViewModel. But i dont have an idea how to do it! I was thinking about to notify the button via my INotifyChanged event from the property that is shown in the textbox. But i couldnt find out how.
Followed some code. But, sorry, there is nothing beside the textbox and the button since i couldnt find a way to solve that.
<TextBox Name ="tbCounter" Text ="{Binding CalcViewModel.Counter, Mode=OneWay}" Background="LightGray" BorderBrush="Black" BorderThickness="1"
Height="25" Width="50"
commonWPF:CTextBoxMaskBehavior.Mask="Integer"
commonWPF:CTextBoxMaskBehavior.MinimumValue="0"
commonWPF:CTextBoxMaskBehavior.MaximumValue="1000"
IsReadOnly="True"/>
<Button Name="btnResetCount" Focusable="True" Content="Reset" Command="{Binding Path=CalcViewModel.ResetCounter}" Style="{StaticResource myBtnStyle}"
Width="100" Height="25">
Is there a common way to set the IsEnabled property of a control based on a property/value in the XAML or in the ViewModel?
EDIT This is my ViewModel, i extracted the related members and properties only otherwise the post would be too long:
class CalcViewModel:INotifyPropertyChanged
{
private CCalc _calc;
public int Counter
{
get
{ return _calc.Counter; }
set{ _calc.Counter = value;}
}
public event PropertyChangedEventHandler PropertyChanged;
void ResetCounterExecute()
{ _calc.Counter = 0; }
bool CanResetCounterExecute()
{
if (_calc.Counter > 0)
{ return true; }
else
{ return false; }
}
public ICommand ResetCounter
{ get { return new RelayCommand(ResetCounterExecute, CanResetCounterExecute); } }
public CCalcViewModel()
{
this._calc = new CCalcViewModel();
this._calc.PropertyChanged += new PropertyChangedEventHandler(OnCalcPropertyChanged);
}
private void OnCalcPropertyChanged(object sender, PropertyChangedEventArgs e)
{
this.RaisePropertyChanged(e.PropertyName);
}
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
You want to combine an element property binding:
IsEnabled={Binding ElementName=Textbox, Path=Text}
With a valueconverter
IsEnabled={Binding ElementName=Textbox, Path=Text, Converter={StaticResource IsAtLeastValueConverter}}
IsAtLeastValueConverter.cs
namespace WpfPlayground
{
public class IsAtLeastValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (System.Convert.ToInt32(value) > 5)
{
return true;
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return Binding.DoNothing;
}
}
}
Oh I forgot you'll need to add this to your control:
<Window.Resources>
<wpfPlayground:IsAtLeastValueConverter x:Key="IsAtLeastValueConverter" />
</Window.Resources>
Edit: VM Version
I've put in elipsis (...) where I didn't make changes to your code.
<Button ... IsEnabled={Binding Path=ButtonIsEnabled} ...>
class CalcViewModel:INotifyPropertyChanged
{
private CCalc _calc;
private bool _buttonIsEnabled;
public ButtonIsEnabled {
get { return _buttonIsEnabled; }
set {
_buttonIsEnabled = value;
RaisePropertyChanged("ButtonIsEnabled");
}
}
public int Counter
{
get
{ return _calc.Counter; }
set{
_calc.Counter = value;
_buttonIsEnabled = _calc.Counter > 5;
}
}
...
}
So what happens here is when you change the counter value, you set the ButtonIsEnabled property which raises the property changed event and updates the button on the form with whatever logic you're using to determine if the button should be enabled.
Edit: You might need to remove that Binding=OneWay from the textbox, I'm not sure if it will initiate the set property if you're using that setting.
If you wish to do it directly in the XAML (I wouldn't necessarily recommend this, as validation should probably be done in the view model), you can also use a package, such as https://quickconverter.codeplex.com/ - this allows you to write some C# (ish) in a binding.
I've used it before, and it can make it pretty easy, eg you install the package, add the line to the very start of your application:
QuickConverter.EquationTokenizer.AddNamespace(typeof(object));
which adds the System namespace to QuickConverter (the line above works as object is in the System namespace), and then you can simply do:
IsEnabled="{qc:Binding 'Int32.TryParse($P) && Int32.Parse($P) >= 3', P={Binding ElementName=tbCounter, Path=Text}}"
If & breaks your Intellisense, you can instead write:
IsEnabled="{qc:Binding 'Int32.TryParse($P) ## Int32.Parse($P) >= 3', P={Binding ElementName=tbCounter, Path=Text}}"
(Where 3 is the value you're testing against).
EDIT:
Sorry, on re-reading you XAML, it can be written even more straightforwardly as follows:
IsEnabled="{qc:Binding '$P >= 3', P={Binding CalcViewModel.Counter}}"
You should change your ViewModel to something like this
public class ViewModel:INotifyPropertyChanged
{
public int Counter
{
get { return _counter; }
set {
_counter = value;
RaisePropChanged("Counter");
//for example
if (value>3)
{
IsButtonCounterEnabled = true;
}
else
{
IsButtonCounterEnabled = false;
}
}
}
public bool IsButtonCounterEnabled
{
get { return _IsButtonCounterEnabled; }
set { _IsButtonCounterEnabled = value;
RaisePropChanged("IsButtonCounterEnabled");
}
}
private void RaisePropChanged(string propName)
{
PropertyChanged(this,new PropertyChangedEventArgs(propName));
}
public event PropertyChangedEventHandler PropertyChanged = delegate{};
private int _counter;
private bool _IsButtonCounterEnabled;
}
and then Bind your button like this
<Button IsEnabled="{Binding IsButtonCounterEnabled,Mode=OneWay}" Content="Button" HorizontalAlignment="Left" Height="47" Margin="215,57,0,0" VerticalAlignment="Top" Width="159"/>
Hope this help

Data binding in WPF on button click

I am trying to implement data binding, and to have TextBox's text to be update once I click on some button.
XAML:
<TextBox Text="{Binding Path=Output}" />
Code:
public MainWindow()
{
InitializeComponent();
DataContext = Search;
Search.Output = "111";
}
public SearchClass Search = new SearchClass();
private void button1_Click(object sender, RoutedEventArgs e)
{
Search.Output = "222";
}
public class SearchClass
{
string _output;
public string Output
{
get { return _output; }
set { _output = value; }
}
}
When I execute the program, I see "111", so the binding from MainWindow() works, but if I click a button - the text in the TextBox is not updated (but in the debugger I see that button1_Click is executed and Search.Output is now equal to "222"). What am I doing wrong?
You should implement INotifyPropertyChanged in your SearchClass and then in setter raise the event:
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public string Output
{
get { return _output; }
set
{
_output = value;
PropertyChanged(this, new PropertyChangedEventArgs("Output"));
}
}
If I understood right, SearchClass is the DataContext for your TextBlock. In this case implementing as above would help.
When WPF see some class as the source of Binding - it tries to cast it to INotifyPropertyChanged and subscribe to PropertyChanged event. And when event is raised - WPF updates the binding associated with sender (first argument of PropertyChanged). It is the main mechanism that makes binding work so smoothly.
You have to implement the INotifyPropertyChanged interface on your SearchClass class. This is how binder values are notified their source values have changed. It displays the "111" value because it hasn't been laid out yet (more or less), but will won't update after that until you implement that interface.

Categories

Resources