I'm using a basic TextBox that is bound to an object. Basically, what I want to do is call a method every time the text in the box is edited and the user de-selects the box or when the underlying bound data is edited. What I am using right now is the TextChanged event, but this has a few problems:
It is called when the TextBox is first created, and I don't want this.
It is called every time a new character is added, and I only want it called when the underlying bound data is changed (which seems to be whenever focus shifts from the box).
How can I accomplish this?
EDIT: I've tried several other TextBox properties like Get/LostFocus but they never seem to fire.
Also, I don't want to put this method call in the Setter of the Property, because the underlying data is something that is logically separate from the UI of this project and I don't want any method calls that relate to doing computations for the UI.
The event LostFocus fires when the focus is shifted from the current element. I tried it and its working fine.
As jods says, the best way to bind your TextBox's Text to ViewModel's property. The Code are:
View:
<TextBox x:Name="TextBox1" Text="{Binding Path=Text1,Mode=TwoWay, UpdateSourceTrigger=LostFocus}"/>
ViewModel:
public string Text1
{
get { return _text1; }
set
{
_text1 = value;
RaisePropertyChanged("Text1");
}
}
View code behind:
private void ViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Text1")
{
//Call UI related method...
}
}
In this way, it satisfy your two conditions:
1. Every time when you edit TextBox and lose the focus, Setter of Text1 will be called and ViewModel will raise PropertyChanged event.
2. When underlying Text1 is changed. Text1 will also raise the event so View can know it.
Also it can avoid your two concerns:
1. In the first time binding, only getter of Text1 is called. No event is raised.
2. Setter of Text1 is only called after TextBox is lost focus.
every time the text in the box is edited and the user de-selects the box
Hmmm AFAIK it's a standard behaviour of TextBox if you bind text like that: Text={Binding Property}
when the underlying bound data is edited
You can provide this functionality inside setter of your property.
Best design is to listen for changes in the underlying bound property. You can do that without changing the setter if you use a DependencyProperty or if your object implements INotifyPropertyChanged.
When the underlying property changes (LostFocus by default, or each char at a time) is a binding option.
If you don't want to follow my advice of listenning for changes in your (view-)model, you could subscribe to GotFocus and LostFocus events. Save the current value when you get focus, compare with current value when you lose it. If it's different -> do what it is you want to do.
I am not sure what you are finally trying to achieve but I am going to take a guess at this. If you are following an MVVM pattern then, then it seems like you can achieve what you want by using the updateSourceTrigger property of the binding. If you are not using MVVM then you might what to take a look at using MVVM
Related
So suppose there is an enum
enum SampleEnum
{
Item1,
Item2
}
Then there is a ComboBox
<ComboBox ItemsSource="{Binding SomeItemSource}"
SelectedItem="{Binding
Path=ItemX,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged,
Converter={StaticResource ResourceKey=SomeConverter}}">
Combo box has a ViewModel as its DataContext
ViewModel : INotifyPropertyChanged, ...
{
...
public SampleEnum ItemX
{
get => model.GetItemX();
set
{
model.SetItemX(value);
RaisePropertyChanged();
}
}
...
}
And RaisePropertyChanged([CallerMemberName] string caller = "") invokes PropertyChanged with property's name.
But.
When I run my code, open my CheckBox, select one item, I get following behaviour: my code enters setter, sets value of the model, raises PropertyChanged, then my getter is invoked, value is retrieved, but it never reaches ComboBox. ComboBox displays the value I chose by hand, not the value returned by accessor.
E.g. if you rewrite get => SampleEnum.Item2 to return always the same value, ComboBox will still display the value I picked by my hand in UI, not the one that is returned by accessor, even though I am 100% sure that getter is invoked, than the value travels to Converter and Convrter also returns proper value.
But if RaisePropertyChanged(nameof(ItemX)) is invoked from any other place, ComboBox immediately retrieves the value from accessor and displays it.
In short, ComboBox ignores PropertyChanged if invoked from setter, but in any other case it works perfectly fine. Specifying directly the name of property (instead of relying on compiler services) or invoking several RasiePropertyChanged in a row in setter does not work.
In general, one should expect that the value selected in combo box and the one returned by getter are the same, but sometimes model can reject provided value and instead return a default one. Not the best behaviour, but it is possible. And in this case the user will be misinformed which item of a ComboBox is actually selected.
I am just wondering what is so special about this accessor that ComboBox ignores it.
tl;dr: What you're trying to do is data validation. That's a solved problem: You can implement validation in your viewmodel with IDataErrorInfo, or in your view with ValidationRules. Either one of those works with WPF instead of against it. Working against WPF is almost invariably losing proposition.
You could also write an ItemContainerStyle for the ComboBox that disables invalid items, or your viewmodel could update the ComboBox item collection to exclude any items that are currently unselectable. I prefer that approach: Rather than "here, you can choose any of these options -- BZZZT, LOL, WRONG CHOICE!", it seems friendlier to present them only with the options that they can choose.
And if you'll know which options are valid after they make their choice, you can almost certainly know beforehand as well.
ComboBox ignores PropertyChanged if invoked from setter, but in any other case it works perfectly fine.
That's correct. The ComboBox is still handling the change it got from the user, and won't be finished doing so until some time after your setter is complete. The ComboBox will ignore any PropertyChanged events on a property it's currently updating. This is by design.
The standard workaround is to call BeginInvoke() with ApplicationIdle priority, and raise PropertyChanged in the delegate. That will have the effect of raising PropertyChanged again after the ComboBox is entirely finished with the current selection-change event. But that's nothing a viewmodel should ever be concerning itself with.
As you say, "Not the best behaviour". It would be preferable to write validation which rejects the wrong value in the first place, rather than writing a strange workaround like the above.
I have a two way binding on a string dependency property to the Text of a TextBox.
The binding looks like this:
Text="{Binding MyValue, Mode=TwoWay}"
I have some code that fires in OnKeyUp (in an attached property) that sets MyValue. But rather than taking my value and applying it to the Text property, it is going the other way around.
Here are the sequence of events:
The user enters a string of text that looks like this: 0299900650
The user presses enter.
In my on key up I set MyValue to 2999 and move to the focus to the next field.
The setter for MyValue fires and does a NotifiyPropertyChanged with 2999
The LostFocus fires.
The setter for MyValue fires and does a NotifiyPropertyChanged with 0299900650.
It seems to me that my value is not making it back to "TextBox.Text" somehow. When I loose focus the TextBox is updating the value of the string with what it has in the Text property (the unchanged value because my change did not get back.)
Is there some WPF magic I am not aware of here?
NOTE: I have double checked my bindings. (Besides they work from the UI so I imagine they are correct going to the UI.)
NOTE II: When I say the "user enters a string", it is really a scanned barcode. But since I am using a keyboard wedge, it is as if it was typed in. (However, the whole point of setting the value is because I am doing something different if the user scans as apposed to typing.)
UPDATE: I found this was due to another property having a side effect. I was able to fix the problem.
You simply jump into the concurency conflict on WPF binding messaging.
To prove that, do the following:
remove your OnKeyUp even handler
and do the same you did. On Enter click binding mechanism fires and sets your code behind property.
In case when you make on KeyUp, you new value 2999 is surpressed by the binding after.
To manage this correctly make use of Converter and get rid of OnKeyDown even subscription.
I found this was due to another property having a side effect. I was able to fix the problem by fixing that side effect.
I would like to know if it is possible to make a difference in the event selection change of a combobox.
I want to make a difference between a user that manually click the combobox and changes its value and a change in the selection that i do from code.
ie :
if i click my combobox and change its value by hand, the event is fired
but if i do myCombobox.selectedItem=1 [edit] the events is not fired
Is there an event that has this behaviour in a wpfcombobox ?
If not, do you have any idea on how to do that ?
Thanks
[edit] or if it is the binding of the combobox that changes its value
You are dealing with a couple of different scenarios, both of which are solvable.
1) Don't process SelectedItem requests during databinding. You have at least two options here:
a) Don't add the event handlers to the control until after databinding is complete or the form is loaded (depending on whether or not databinding is automatic or manual).
b) Set a form level property indicating when it is ok to process the SelectedItem event. You will probably want to set this to true after the form is loaded or after the databinding is complete. In your SelectedItem code, don't perform any actions unless this property is true.
2) Process the SelectedItem logic if the SelectedItem is changed programatically. Again, two options:
a) Extract your logic from the SelectedItem event into a method and then call this method when you perform the logic to set the selected item.
b) Create a custom combobox that inherits from the base and add a SetSelectedItem method (for example) to this inherited combo. This method would then raise the SelectedItem method. This would be reusable and you wouldn't have to remember to do 2 pieces of work whenever you set the SelectedItem manually.
Before you change the selecteditem in code, remove the event handler with -=, then add it back afterwards.
Ok, here's the deal.
I have a graph, that "listens" to some labels' textfields, and graphs their values. It does this by adding the values on the TextChanged event.
The problem:
TextChanged only fires when the text has actually changed (obviously), and not when it has been assigned a new value.
What I need:
A way to detect if the Text field of label has been updated (assigned to), and all I have to work with is a reference to the Control (ie the Label).
I realize TextChanged wasn't designed for this, which is why I'm wondering if any of you guys have encountered a similar problem, and found a solution to it.
The reason that your TextChanged event handler is not invoked in initial assignment of the text is that it is attached after the first text has been assigned (the designer seems to do things in this order by default). I would do like this:
TextChanged calls another method (UpdateGraph)
UpdateGraph collects necessary data and updates the graph
Call UpdateGraph as the last thing done when loading the form
That will make sure that the graph is updated with the initial values. It is important that this call happens after the call to InitializeComponent.
It is fairly common practice in "setters" to ignore trivial changes, for example:
public int Foo {
get {return foo;}
set {
if(foo != value) {
foo = value;
OnFooChanged();
}
}
}
If Text is behaving like this, and you are relying on all updates causing an event, then it may not work as you want. I would probably try to find another way to do what you want; perhaps using an intermediate object that passes the values through.
You could create your own control that inherits from that control and create your own Text property that will fire an event when assigned a new value.
I have a WPF Window that contains a TextBox. I have implemented a Command that executes on Crtl-S that saves the contents of the window. My problem is that if the textbox is the active control, and I have newly edited text in the textbox, the latest changes in the textbox are not commited. I need to tab out of the textbox to get the changes.
In WinForms, I would typically call EndEdit on the form, and all pending changes would get commited. Another alternative is using onPropertyChange binding rather than onValidation, but I would rather not do this.
What is the WPF equivalent to EndEdit, or what is the pattern to use in this type of scenario?
Thanks,
Based on Pwninstein answer, I have now implemented an EndEdit in my common class for WPF Views / Windows that will look for bindings and force an update on them, code below;
Code below;
private void EndEdit(DependencyObject parent)
{
LocalValueEnumerator localValues = parent.GetLocalValueEnumerator();
while (localValues.MoveNext())
{
LocalValueEntry entry = localValues.Current;
if (BindingOperations.IsDataBound(parent, entry.Property))
{
BindingExpression binding = BindingOperations.GetBindingExpression(parent, entry.Property);
if (binding != null)
{
binding.UpdateSource();
}
}
}
for(int i=0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
this.EndEdit(child);
}
}
protected void EndEdit()
{
this.EndEdit(this);
}
In my Save command, I now just call the EndEdit method, and I don't have to worry about other programmers selection of binding method.
To avoid the issue of needing to tab away, you could simply change the UpdateSourceTrigger property of your controls' binding. Try the following:
<TextBox.Text>
<Binding Path="MyProperty" UpdateSourceTrigger="PropertyChanged"/>
</TextBox.Text>
This tells WPF to update the backing object whenever the Text property is changed. This way, you don't need to worry about tabbing away. Hope this helps!
EDIT:
The accepted answer for the following SO question provides a way to automatically run validation rules for a page. You could modify it to call UpdateSource() on all BindingExpression objects instead.
Link
You can force specific bindings to update using code like the following:
var bindingExpression = txtInput.GetBindingExpression(TextBox.TextProperty);
bindingExpression.UpdateSource();
Doing this more generally is difficult because there is no generic way to get all bindings, nor would you necessarily want them all to updated.
I disagree w/ArielBH. The issue here is the interplay between keyboard and logical focus, and unless you have changed all your Data Binding update triggers to PropertyChanged, you may miss some source data updates in certain scenarios (e.g. Toolbar button clicks). For instance, the default update trigger for TextBox.Text is LostFocus and clicking on a toolbar button does not blur the active TextBox focus.
If you have some mechanism to register the controls, then you could explicitly force the data binding to update the source in the same place you'd be calling EndEdit in a WinForms app. It's not neat or elegant, but it gets the job done.
If somebody has come up with a better solution, I'd be all ears as well.
HI,
Well, while using WPF, one needs to adopt to a different mindset.
I would basically bind the TextBox's Text property to one of my properties (Model, ViewModel, Code-Behind, whatever makes you happy).
So when you handle CTRL+S you just go to the clr property that is binded and continue happily with all the data you want.
Hope that help you, if you require code examples, leave me a comment.
Ariel
I believe you are supposed to declare a binding group and then reference that binding group in code. I put mine on the root Window element so it gets the bindings of all the controls on the Window.
<Window.BindingGroup>
<BindingGroup />
</Window.BindingGroup>
this.BindingGroup.CommitEdit();