I have several controls including a DataGrid that I want to be disabled until there is a valid value in the first TextBox in the presentation. So I added a boolean property to bind to in the VM and bind to it in the xaml (below).
The binding works, but has the side effect of 'trapping' the user in the TextBox (MoneyToAllocate).
Presumably this is because the TB binding is LostFocus and there is no place for the focus to go and actually trigger the updates. What's a good way to fix this?
Cheers,
Berryl
ViewModel
public bool HasMoneyToAllocate { get { return MoneyToAllocate.Amount > 0; } }
public Money MoneyToAllocate {
get { return _moneyToAllocate; }
set {
if (value.Amount < 0) return;
_moneyToAllocate = new Money(value.Amount, SelectedCurrency);
NotifyPropertyChanged(() => HasMoneyToAllocate);
}
}
View
<TextBox Text="{Binding MoneyToAllocate, Converter={StaticResource moneyConverter}}" />
<DataGrid IsEnabled="{Binding HasMoneyToAllocate}" ...
EDIT
I should have added that I tried PropertyChanged for update but it gets a bit messy since the value of the text box needs to be formatted by the converter. Any other ideas?
FINAL EDIT
I wound up letting another control that previously wasn't a tab stop be a tab stop, so the text box had a place to go. Phil understood the problem best and gets the answer, even though the range of values the user can input (.001 to decimal.MaxValue) make an up-down impractical.
Use UpdateSourceTrigger=PropertyChanged
<TextBox
Text="{Binding MoneyToAllocate, UpdateSourceTrigger=PropertyChanged,
Converter={StaticResource moneyConverter}}" />
Then you have to use UpdateSourceTrigger=PropertyChanged
- if you use that binding you are using the value in the VM will not effected till the focus moves from the textBox
- but if you add UpdateSourceTrigger=PropertyChanged to your binding the VM property (MoneyToAllocate) will effected immediately (when the textBox.Text value changed)
Related
I'm facing an odd issue with my WPF (MVVM) project.
I have a few controls which bind to the properties in the ViewModel. INotifyPropertyChanged is configured, everything (initially works). I type in some values into my controls and I click a button. I can see, by stepping through the code, all the property values are what they should be. So far, it is text book.
Now I notice the issue. After I click the button, some logic is performed, such as saving these values to a database. I can then edit the control values and then save to the database again. The properties at this point to do not update.
Binding clearly works, because the output shows no binding errors and when I click the Save button, the properties are correct. However, after I click the save button, and then change the property values, the properties are not updatdd. I cannot fathom why this is the case.
As a trial, I added the PropertyChanged to the update source trigger and this seems to fix the issue, however, I've never had to do this before. Any ideas what could be wrong?
I don't believe the answer is 2 way binding (I am happy to be wrong) because it binds!
<TextBox Text="{Binding DataSource, UpdateSourceTrigger=PropertyChanged}" Grid.Row ="1" Grid.Column="2" />
Where as normally I would use
<TextBox Text="{Binding DataSource}" Grid.Row ="1" Grid.Column="2" />
UpdateSourceTrigger property determines the time, when the binding has to be updated. The default value for this property is LostFocus. So by default, after you type something and move the focus out, the binding will update. If you set the property value to PropertyChanged, binding will update immediately once you entered the value in text box.
http://msdn.microsoft.com/en-us/library/system.windows.data.binding.updatesourcetrigger(v=vs.110).aspx
In your case, the binding is updated on button click, since focus transferred to Button from textbox. Once the UpdateSourceTrigger set to PropertyChanged, the binding will update on every text change.
I Have a text box, depending on the text box value i need to enable/disable other text Boxes.I am using MVVM Pattern.
So here's my problem , whenever i enter some text in TextBox1 ,the Setter for TextBox1 is getting fired and i am able to check in if loop ,whether valus exists and i am disabling other text boxes. Now, when there is single value in text box say "9" and i am deleting / Backspacing it the Set event is not getting triggered in order to enable the other Text Boxes.
View:
<TextBox Text = {Binding TextBox1 , UpdateSourceTrigger = PropertyChanged,Mode= TwoWay}/>
<TextBox Text = {Binding TextBox2 , UpdateSourceTrigger = PropertyChanged,Mode= TwoWay}/>
<TextBox Text = {Binding TextBox3 , UpdateSourceTrigger = PropertyChanged,Mode= TwoWay}/>
View Model:
private int_textBox1;
public int TextBox1
{
get {return _textBox1;}
set
{
_textBox1= value;
if(value > 0)
{
//Code for Disabling Other Text Boxes (TextBox2 and TextBox3)
}
else
{
// Code for Enabling Other Text Boxes (TextBox2 and TextBox3)
}
NotifyPropertyChanged("TextBox1");
}
}
If you are using MVVM pattern, you should create boolean properties, and bind TextBox.IsEnabled property to it. Your boolean properties should raise PropertyChanged event, in order to tell the view (TextBox in your case) that your property was really changed:
public bool IsEnabled1
{
get { return _isEnabled1; }
set
{
if (_isEnabled1 == value)
{
return;
}
_isEnabled1 = value;
RaisePropertyChanged("IsEnabled1");
}
}
and then in xaml:
<TextBox Text="{Binding TextBox1, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
IsEnabled="{Binding IsEnabled1}" />
and so on with other TextBoxes
first of all, if you set your updatesourcetrigger to Propertychanged - your setter is called when you do anything in your textbox. i check this in a simple testproject. btw do you call OnPropertyChanged in your setter, because its not in your sample code?
if not your binding seems to be broken. so pls check your binding or post some more relevant code.
EDIT:
if you change your type to int? you can do the following xaml
<TextBox Text="{Binding MyNullableInt, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, TargetNullValue=''}"/>
I've got a form that gets given a datarow from a dataset to bind all its elements. One of them is a bool, but I want that bool to be represented by by a Yes/No combo box. So I did this and it works nicely.
I also want to bind the visibility of a couple elements to this bool field. When the form loads, the initial setting of the visibility works. When I change the combobox selection, the ConvertBack() method of the ComboBox gets called (i.e. it's setting the bound value). But the other elements that have their visibility bound to that same field don't get updated. I set breakpoints in the Conversion methods and they never get called like they do when the form loads.
Here's the relevant XAML:
<ComboBox SelectedIndex="{Binding Path=[Adequate], Converter={StaticResource b2iConverter}}" Name="cb_Adequate" >
<ComboBoxItem>Yes</ComboBoxItem>
<ComboBoxItem>No</ComboBoxItem>
</ComboBox>
<Label Content="Reason:"
VerticalAlignment="Center"
Visibility="{Binding Path=[Adequate],
Converter={StaticResource b2vConverterInverse}}"/>
<TextBox Text="{Binding Path=[NotAdequateReason]}"
Visibility="{Binding Path=[Adequate],
Converter={StaticResource b2vConverterInverse}}"/>
"Adequate" is the bool field
b2iConverter is just booleanToIndexConverter (from the above link)
b2vConverterInverse is just an inverted boolean to visibility converter (I want the label and textbox shown when Adequate is FALSE or 0).
Thanks for any help. I can post more code if needed, I figure the problem is in the XAML...
EDIT: Apparently it's not possible with XAML (see Greg's post below), so I just do it in code:
private void cb_Adequate_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Visibility vis = (cb_Adequate.SelectedItem as ComboBoxItem).Content.ToString() == "Yes" ? Visibility.Collapsed : Visibility.Visible;
label_Reason.Visibility = tb_AdequateDesc.Visibility = vis;
}
If you want your UI elements to change state when a data property changes, you need to implement INotifyPropertyChanged on your data class.
This means that you can't use the DataRow for your purposes. You'll have to create a new class, then at run time populate it with values from the DataRow and then bind that object to your view.
To set up a ReadOnly ComboBox in XAML (WPF), you have to set up a ComboBox and a TextBox showing only one of them according to a pair of properties IsReadOnly/IsEditable that must exist on your ViewModel. Note that on this sample "UserNVL" must exist in the resources and it should be a NameValueList collection that allows us to convert ID to names. In this case the RecipientID is the key for a user name. Note also the VisibilityConverter must also exist in the resources and it's a standard BooleanToVisibilityConverter.
Gosh! This was so hard to find I had to made it myself. This allows the user the select the content of the text box. No way a disabled ComboBox would ever allow you to do it.
There are two properties named IsHitTestVisible & IsTabVisible. the former makes the control deaf to mouse events and the latter to keyboard events.
This could help you as it would not give the disabled look to your combo box but you will succeed in making a read only combo box..
Source :-
http://www.telerik.com/community/forums/wpf/combobox/isreadonly-does-seem-to-work.aspx
Why not just set IsEnabled=false?
<DockPanel>
<TextBlock Text="Recipient" Margin="6,9,3,6" HorizontalAlignment="Right"/>
<ComboBox
x:Name="RecipientID"
ItemsSource="{Binding Source={StaticResource UserNVL}}"
DisplayMemberPath="Value"
SelectedValuePath="Key"
SelectedValue="{Binding Path=RecipientID}"
Height="20"
Margin="6,6,0,6"
MinWidth="200"
HorizontalAlignment="Left"
IsEditable ="True"
Visibility="{Binding Path=IsEditable, Converter={StaticResource VisibilityConverter}}"/>
<TextBox
x:Name="RecipientName"
Text="{Binding ElementName=RecipientID, Path=Text}"
Margin="6,6,0,6"
MinWidth="200"
HorizontalAlignment="Left"
Style="{StaticResource textBoxInError}"
Visibility="{Binding Path=IsReadOnly, Converter={StaticResource VisibilityConverter}}"/>
</DockPanel>
I think that you will find it much easier and practical to create a class to extend the ComboBox class in this very simple manner:
override the OnSelectionChanged method of the Combobox to check the property IsReadOnly before to allow base.OnSelectionChanged(e) to run.
That way you just have to set ComboBox.IsReadOnly property to True. No big XAML to write everywhere...
Here is a custom ComboBox subclass that gives the read only behaviour I needed for my scenario:
public class ReadOnlyComboBox : ComboBox
{
static ReadOnlyComboBox()
{
IsDropDownOpenProperty.OverrideMetadata(typeof(ReadOnlyComboBox), new FrameworkPropertyMetadata(
propertyChangedCallback: delegate { },
coerceValueCallback: (d, value) =>
{
if (((ReadOnlyComboBox)d).IsReadOnly)
{
// Prohibit opening the drop down when read only.
return false;
}
return value;
}));
IsReadOnlyProperty.OverrideMetadata(typeof(ReadOnlyComboBox), new FrameworkPropertyMetadata(
propertyChangedCallback: (d, e) =>
{
// When setting "read only" to false, close the drop down.
if (e.NewValue is true)
{
((ReadOnlyComboBox)d).IsDropDownOpen = false;
}
}));
}
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
if (IsReadOnly)
{
// Disallow changing the selection when read only.
e.Handled = true;
return;
}
base.OnSelectionChanged(e);
}
}
Points about this approach:
Doesn't break any existing styles applied to the element, unlike an approach that introduces additional UI elements.
Doesn't break input focus while read only. You can still tab into and click to focus this element. This is more accessible, which is a concern in my scenario.
The UI element doesn't, but default, look any different when read only. If you need that, you would have to apply relevant styles to make it so.
If IsEnabled is set to false, Combobox value is nearly not readable. What I found as suitable solution is:
combobox and textbox (formated as readonly) are in the same grid position
combobox spans to next column to gain additional 15 width so dropdown button is visible
textbox.IsVisible is bound to combobox.IsEnabled with bool to visibility converter.
textbox.Text is bound to combobox.SelectedItem (in my case it is strongly typed so I actually bound into .DisplayText of it)
At first I want to say that sample below is oversimplification.
Suppose you have bound WPF control.
<Window Title="Window1" Height="300" Width="300">
<Grid>
<StackPanel>
<TextBox Text="{Binding Name}" Margin="10"/>
<Button HorizontalAlignment="Center"
Content="Click Me" Margin="5"
Padding="2" Click="OnButtonClick" />
</StackPanel>
</Grid>
</Window>
Window is bound to the Person class which implements INotifyPropertyChanged and has Name setter in form
public string Name
{
get { return _name; }
set
{
_name = "Some Name";
OnPropertyChanged("Name");
}
}
I.e. _name is assigned "Some Name" whenever user tries to change it from UI.
But this sample does not works. I changed name in TextBox to some value press tab forcing focus to move to the Button and value in TextBox remains unchanged although PropertyChanged event was triggered.
Could you please explain me why it happens? As I understand PropertyChanged event forces UI to reread values from properties and display them but in my example value in databound textbox is not updated.
Again. I understand that this is poor implementation of the property and but I want to repeat that this is oversimplification.
It is just a sample.
But anyway, PropertyChanged signals that property was changed and should be updated but it does not.
The PropertyChanged event is ignored by the TextBox because it is the initiator of the event.
Some clarification:
The TextBox (or the binding on the textbox) knows it is the initiator because it receives the PropertyChanged event in the same call. By doing an asynchronous call, the textbox (or binding) has no way to know that it is the initiator, so it will process the event as if someone else has updated it
If you add a 2nd textbox to your UI, you'll see that the 2nd TextBox does change when you edit the 1st, and the other way around.
The dummy converter workaround suggested by Heinzi (described here) doesn't work when binding's UpdateSourceTrigger is PropertyChanged. But what if this is what we need?
It seems that making the binding asynchrounous does the trick, e.g.:
SelectedIndex="{Binding SelectedIndex, IsAsync=True}"
As Bubblewrap already pointed out, this is by design -- the textbox assumes that if it sets a bound property to some value, the setter will not change the value. According to Microsoft, they won't change this behavior since this would break existing code.
If you want to change the value (yes, there are perfectly good reasons for doing that), you have to use a workaround, for example, by adding a dummy converter. There is a blog entry (not written by me) describing this technique in detail.
The reason is because you have hardcoded the 'Some Name' in the setter. When you changed the textBox value the setter is actually getting called and it again setting "Some Name" as the propertyValue so it doesnt seems to be changed in the UI.
Put _name = value and things will just work as you expected,
public string MyField
{
get { return _myField; }
set {
if (_myField == value)
return;
_myField = value;
OnPropertyChanged("MyField");
}
}
This is the proper implementation of the property.
When you change the property, make sure that the EXACT same instance of the object is binded to a control. Otherwise, the change will be notified but the control will never get it because the control is not binded properly.
Replacing setter in form
set
{
_name = "Some Name";
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.DataBind,
(SendOrPostCallback)delegate { OnPropertyChanged("Name"); },
null);
}
resolves the issue but it is still open. Why should I make async call instead of synchronous signaling that my property has been changed.
If I am not mistaken, the default binding behavior of the Text property on the TextBox is TwoWay, so this should work. You can force it to be TwoWay in the XAML like this:
<Window Title="Window1" Height="300" Width="300">
<Grid>
<StackPanel>
<TextBox Text="{Binding Name, Mode=TwoWay}" Margin="10"/>
<Button HorizontalAlignment="Center"
Content="Click Me" Margin="5"
Padding="2" Click="OnButtonClick" />
</StackPanel>
</Grid>
</Window>
Note the Mode=TwoWay in the Binding declaration.
If that doesn't work, then I suspect that an exception is being thrown in the code that fires the event, or assigns the property and you should look for that.
There seems to be a possibility that you are making the call to change the value on a thread that is not the UI thread. If this is the case, then you either have to marshal the call to fire the property changed event on the UI thread, or make the change to the value on the UI thread.
When an object is bound to a UI element, changes to the object which can affect the UI have to be made on the UI thread.