I'm having a bit of a problem with WPF property binding. First the code.
C#
public partial class WPFTextBox: UserControl
{
private bool _bold;
public bool Bold
{
get { return _bold; }
set
{
_bold = value;
OnPropertyChanged("Bold");
}
}
private bool _selectionChanged;
public WPFTextBox()
{
InitializeComponent();
DataContext = this;
Bold = true; // <--- This works, the checkbox will be checked
_selectionChanged = false;
}
private void txtDetails_SelectionChanged(object sender, RoutedEventArgs e)
{
var selection = txtDetails.Selection;
_selectionChanged = true;
Bold = selection.FontWeight() == FontWeights.Bold;
// ^-- This doesn't work It will trigger everything, but the checkbox won't
// change value. FontWeight() is an extension I wrote
_selectionChanged = false;
}
private void OnPropertyChanged(string name)
{
if(_selectionChanged)
return; // If the change was brought from the user moving the
// cursor in the textbox, don't change the textbox.
TextRange range = txtDetails.Selection;
switch(name)
{
case "Bold":
// change selection to bold, like I mentioned I does work
break;
default:
break;
}
}
}
XAML
<RichTextBox Name="txtDetails" SelectionChanged="txtDetails_SelectionChanged"/>
<CheckBox Name="chkBold" Content="Bold" IsChecked="{Binding Path=Bold}"/>
I'm creating a textbox with format options. The binding works in the constructor, but not in the selection changed event. I've tried adding a lot of options to the binding such as Mode=TwoWay and different property changed triggers.
The reason I'm using the _selectionChanged bool is because if I don't check for that, if I have a word was different formatting such as hello and I click on it, it will change the formatting for all of the word to either bold or not. I think maybe it's because I'm handling it in selection changed event, but then I'm not sure where else I could change property value.
See the example from here, you can just grab the INPC part.
set
{
_bold = value;
OnPropertyChanged("Bold");
NotifyPropertyChanged();
}
You need to inherit INotifyPropertyChanged interface
and implement PropertyChangedEventHandler
public class WPFTextBox: UserControl,System.ComponentModel.INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And call OnPropertyChanged in the setter of your property
1.You can use UpdateSourceTrigger=PropertyChanged event also.
2.Yes binding work on controls from the Extended WPF Toolkit.
IsChecked="{xcd:Path=Bold}"
Related
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.
Trying to bind a text via a string to my xaml and use a propertychanged function to change the text depending on the value of an int.
This is my code:
XAML:
<Label Text = "{Binding Forename}" />
CODE:
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged (string propertyName)
{
var changed = PropertyChanged;
if (changed != null) {
PropertyChanged (this, new PropertyChangedEventArgs (propertyName));
}
}
string info;
int myInt = 0;
public string Forename {
get {
return info;
}
set {
if (myInt == 0) {
info = value;
OnPropertyChanged ("TextOne");
}
else if (myInt == 1)
{
OnPropertyChanged ("TextTwo");
}
else
{
OnPropertyChanged ("TextThree");
}
}
}
I get no text now.
That is not how OnPropertyChanged() works. You shouldn't pass the new text you want displayed as argument, instead you should assign the new text to the info field, and put the name of the property as argument.
What OnPropertyChanged(name) does is tell the XAML that the Property name has changed, on which the XAML will get that property, and update the displayed value accordingly.
public string Forename {
get {
return info;
}
set {
if (myInt == 0) {
info = value;
OnPropertyChanged ("Forename");
}
else if (myInt == 1)
{
info = "TextTwo";
OnPropertyChanged ("Forename");
}
else
{
info = "TextThree";
OnPropertyChanged ("Forename");
}
}
}
Edit
As you had some questions about what you should put into protected virtual void OnPropertyChanged(string propertyName), that function is fine the way it is. When it is called, it will use the PropertyChangedEventHandler PropertyChanged to let all subsribers to the event know that the property propertyName has changed. The XAML {Binding Forename} acts as a subscriber/listener for the PropertyChanged, and will update it's text as soon as it is told that the property to which it is bound ("Forename") has changed. Which is what happend when you make the call OnPropertyChanged("Forename").
Edit 2
It seems you may have forgotten to assign your DataContext
In the code-behind of your XAML (will be named something like mainwindow.xaml.cs)
InitializeComponent();
ViewModel vm = new ViewModel();
DataContext = vm;
vm.Forename = "TestString"
And move the Forename-code stuff into a seperate class, name ViewModel. Like this:
public class ViewModel{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged (string propertyName)
{
var changed = PropertyChanged;
if (changed != null) {
PropertyChanged (this, new PropertyChangedEventArgs (propertyName));
}
}
string info;
int myInt = 0;
public string Forename {
get {
return info;
}
set {
if (myInt == 0) {
info = value;
OnPropertyChanged ("Forename");
}
else if (myInt == 1)
{
info = "TextTwo";
OnPropertyChanged ("Forename");
}
else
{
info = "TextThree";
OnPropertyChanged ("Forename");
}
}
}
}
By looking at your code I suspect that you made the most popular mistake in Binding. You are not raising changes for correct property. The idea is that XAML somehow must know that property changed to update the UI - so instead running in tight loop and check if property value is different than this on the screen Microsoft come with idea of delegate that named PropertyChanged. Mostly used in getter/setters.
You have to be very careful in using this delegate because until version 4 of .NET Framework parameter for delegate was string - the name of the property. Later version of .NET Framework bring the special attribute CallerMemberName - here is example: Jesse Liberty
In you case you rising the property changed event but in parameter you sending names of properties that does not exist :( You should send OnPropertyChanged("Forename") only.
I will try to keep this concise as possible.
I am having an issue where I have a two way databinding with 3 radio buttons. My issue is, I am getting sort of a cyclical change as soon as I change the chosen radio button. So what is happening, I change the radio button which changes the source data property, but the source data property changes the other 2 properties that are bound, which in turn changes the other radio buttons, which calls the PropertyChange function on those properties as well. How can I fix it so that the PropertyChange only happens once per radio button switch.
Property Changed Event Handler:
public class SolutionOptions : INotifyPropertyChanged
{
bool _direct;
bool _iterative;
bool _domain;
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public bool Domain
{
get { return _domain; }
set
{
if (_domain == value) return;
_domain = value;
OnPropertyChanged("Domain");
}
}
public bool Iterative
{
get { return _iterative; }
set
{
if (_iterative == value) return;
_iterative = value;
OnPropertyChanged("Iterative");
}
}
public bool Direct
{
get { return _direct; }
set
{
if (_direct == value) return;
_direct = value;
OnPropertyChanged("Direct");
}
}
Data Binding Code:
this.radioButton_DirectSolver.DataBindings.Add("Checked", _ssf, "SolOptions.Direct");
this.radioButton_IterSolver.DataBindings.Add("Checked", _ssf, "SolOptions.Iterative");
this.radioButton_DomainDecomp.DataBindings.Add("Checked", _ssf, "SolOptions.Domain");
Form Image:
First try adding this to all your setters
if (_domain == value) return;
So you don't call OnPropertyChanged if it does not change.
This is a good practice in general.
Second (only if the first does not work) put each button in a separate group so they don't call each other and handle setting others to false in your code behind.
I have a UserControl that has a Textbox, Button, and a Tooltip controls on it. It does implement INotifyPropertyChanged I have tried overriding the Text property and adding my own property, but in all cases the control reads from the bound data source fine, but never updates the data source. My events are raised when the text is changed. Some of the code is below. All other standard controls are working fine. What do I need to get the control to update the data source when the user has entered or changed the value?
public partial class UrlControl : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[Bindable(true)]
[Browsable(true)]
public string Url
{
get
{
return url.Text;
}
set
{
if (value != url.Text)
{
url.Text = value;
OnPropertyChanged("Url");
}
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
This is the binding code from the form designer.
this.urlControl1.DataBindings.Add(new System.Windows.Forms.Binding("Url", this.customerBindingSource, "First", true, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged));
}
INotifyPropertyChanged is for datasources. It allows your datasource to notify bound controls and other listeners of property changes. However, controls themselves use a different mechanism. It's a bit strange: you create events on your control with the naming convention <PropertyName>Changed. When the value of a property changes, you raise the associated event.
Example:
public string Url
{
get { return url.Text; }
set
{
if (value != url.Text)
{
url.Text = value;
OnUrlChanged(); // raise event
}
}
}
public event EventHandler UrlChanged;
private void OnUrlChanged()
{
// raise the UrlChanged event
if (UrlChanged != null)
UrlChanged(this, new EventArgs());
}
That's all you need to do. The Databinding Fairies will see that event and hook it up when you create the binding.
Here's the topic on MSDN: How to: Apply the PropertyNameChanged Pattern
This should work well for reading values from the datasource.
However, when it comes to writing values to the datasource it looks like you're storing and getting the Url value directly from the url textbox. However, you're not raising property change notifications when the textbox's text is changed within the UI. To fix this, add a TextChanged event handler on the textbox, which can simple call:
void url_TextChanged(object sender, EventArgs e)
{
OnPropertyChanged("Url");
OnUrlChanged(); // See additional note below
}
As a side, although implementing INotifyPropertyChanged should work... When it comes to Windows Forms binding you can also create an event with the property name suffixed with "Changed" and the binding should watch that:
public event EventHandler UrlChanged;
protected virtual void OnUrlChanged()
{
var handler = UrlChanged;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
[Bindable(true)]
[Browsable(true)]
public string Url
{
get
{
return url.Text;
}
set
{
if (value != url.Text)
{
url.Text = value;
OnPropertyChanged("Url");
OnUrlChanged();
}
}
}
I have a Custom Control (Windows Form) that is a lookup text box. A property on the Control is Current Selection which is a Custom Object containing "Identifier", "Code" and "Description". This property is Databound using a BindingSource.
Displaying the information works great. On the other hand regardless of whether I set the Update to OnValidate or OnValueChange it never updates the BindingSource. Is there something I'm missing to get this to auto update?
private System.Windows.Forms.BindingSource buildPlanComponentDataBindingSource;
public void LoadBuildPlan(string itemNumber)
{
var buildPlanComponents = BuildPlan.LoadBuildPlanComponents(itemNumber, AutomaticPrice);
buildPlanComponentDataBindingSource.DataSource = buildPlanComponents;
AssemblyNumber = itemNumber;
}
[Bindable(true)]
[DefaultValue(null)]
public ILookupSelection CurrentSelection
{
get
{
if (currentSelection == null)
currentSelection = new LookupSelection {Code = txtLookup.Text};
return currentSelection;
}
set
{
if (value == null) return;
currentSelection = value;
SetText(currentSelection, DisplayText);
SetDescription(currentSelection, DisplayDescription);
}
}
Implementing INotifyPropertyChanged seems to be the solution!
#region IPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (null != PropertyChanged)
{
PropertyChanged(this, e);
}
}
#endregion
Displaying the information works
great. On the other hand regardless of
whether I set the Update to OnValidate
or OnValueChange it never updates the
BindingSource.
Looking at your code I'm actually not sure of this. In your set, you test for null and abandon; if the data actually contains null (which is what you're describing) your control will be out of synch. I wonder if perhaps that check is masking the underlying problem.
Maybe you need to cause the DataBinding to write its value for each control whose value you are setting this way?
Assuming one data binding for a textbox named txtMySetValue:
txtMySetValue.DataBindings[0].WriteValue();