Within a property's setter, sometimes I must change the property's value to something other than the one currently being set. This doesn't work by simply raising the PropertyChanged event, for a Windows Store app anyway. I can change the value, but the UI doesn't update itself based on that change.
<TextBox Text="{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
The view-model:
class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string text;
public string Text
{
get { return text; }
set
{
if ( text != value ) {
text = value == "0" ? "" : value;
OnPropertyChanged();
}
}
}
protected void OnPropertyChanged( [CallerMemberName] string propertyName = null )
{
PropertyChanged?.Invoke( this, new PropertyChangedEventArgs( propertyName ) );
}
}
I wrote the following method which fixes this problem fairly well:
protected void OnPropertyChanged_Delayed( [CallerMemberName] string propertyName = null )
{
Task.Delay( 1 ).ContinueWith(
t => OnPropertyChanged( propertyName ),
TaskScheduler.FromCurrentSynchronizationContext()
);
}
I call this instead of OnPropertyChanged. But I recently discovered an intermittent bug in my Windows Store app caused by the OnPropertyChanged_Delayed call occurring after the user has navigated away from the page. This leads me to seek an alternative solution.
Is there a better way to change a property's value from within its setter and notify the UI of the change? I am currently developing a Windows Store app, so I'd like an answer in that context. But I will eventually port it to UWP. Is this also a problem on UWP? If so, I'd like a solution there as well (which may be the same).
As I explained in my comment above, I've run into this sort of problem when dealing with the ComboBox control. I want to change the value of the selected item in the setter, but the view ignores my new value. Delaying the PropertyChanged event is an ugly workaround that can cause unintended side effects.
But another way to get around the issue is to bind the SelectionChanged event to a command in your view model.
This XAML binds to the SelectedItem as usual, but also uses System.Windows.Interactivity to bind the SelectionChanged event:
<ComboBox ItemsSource="{Binding MyItems}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SelectionChangedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
In your view model, don't try to override the selected value in the setter. Instead, change it in the ICommand implementation:
string _selectedItem;
public string SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedItem"));
}
}
// RelayCommand is just a custom ICommand implementation which calls the specified delegate when the command is executed
public RelayCommand SelectionChangedCommand
{
get
{
return new RelayCommand(unused =>
{
if (_selectedItem == MyItems[2])
_selectedItem = MyItems[0];
PropertyChanged(this, new PropertyChangedEventArgs("SelectedItem"));
});
}
}
As I said, I've only observed this behavior for the SelectedItem property. If you're having trouble with a TextBox then you might try changing the UpdateSourceTrigger of the binding like this:
<TextBox Text={Binding MyText, Mode=TwoWay, UpdatedSourceTrigger=PropertyChanged} />
My question was: from within a property's setter, how to change the value being set and have the UI recognize that change?
The two-way XAML binding assumes that the target value which is set to the source, is also the value that is actually stored by the source. It ignores the PropertyChanged event of the property it just set, and so it doesn't check the source's actual value after updating it.
TextBox
The simplest solution is: make the binding one-way, and instead update the source in a TextChanged handler.
<TextBox Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}"
TextChanged="TextBox_TextChanged"/>
The event handler:
void TextBox_TextChanged( object sender, TextChangedEventArgs e )
{
var tb = (TextBox)sender;
var pm = (MyViewModel)DataContext;
pm.Text = tb.Text; //update one-way binding's source
}
When the UI's text changes, the event is handled by setting the view-model's text. The view text's one-way binding means that it then receives the value that was actually set. Doing things this way avoids the limitation of XAML bindings mentioned above.
It's possible to leave the binding as two-way and the above solution still works. But if you want to let the two-way binding update its source, then the code is slightly longer:
void TextBox_TextChanged( object sender, TextChangedEventArgs e )
{
var tb = (TextBox)sender;
var pm = (MyViewModel)DataContext;
tb.GetBindingExpression( TextBox.TextProperty ).UpdateSource();
if ( tb.Text != pm.Text )
tb.Text = pm.Text; //correct two-way binding's target
}
If you don't need UpdateSourceTrigger=PropertyChanged then another alternative is:
<TextBox Text="{Binding Text, Mode=TwoWay}" LostFocus="TextBox_LostFocus"/>
with the event handler:
void TextBox_LostFocus( object sender, RoutedEventArgs e )
{
var tb = (TextBox)sender;
var pm = (MyViewModel)DataContext;
tb.GetBindingExpression( TextBox.TextProperty ).UpdateSource();
pm.OnPropertyChanged( "Text" );
}
ComboBox
#RogerN's answer was helpful for showing that the problem can be overcome using SelectionChanged, but he doesn't actually show how to change the value within the setter. I will show that here.
<ComboBox ItemsSource="{Binding Items}"
SelectedItem="{Binding Text, Mode=TwoWay}"
SelectionChanged="ComboBox_SelectionChanged"/>
Add this property to the view-model:
public IList<string> Items
{
get { return items; }
}
readonly IList<string> items = new ObservableCollection<string> { "first", "second", "third", "forth" };
Within the Text property's setter, change the value like this:
text = value == "third" ? "forth" : value;
The first approach for TextBox shown above doesn't work for ComboBox, but the second one does.
void ComboBox_SelectionChanged( object sender, SelectionChangedEventArgs e )
{
var cb = (ComboBox)sender;
var pm = (MyViewModel)DataContext;
cb.GetBindingExpression( ComboBox.SelectedItemProperty ).UpdateSource();
if ( (string)cb.SelectedItem != pm.Text )
cb.SelectedItem = pm.Text; //correct two-way binding's target
}
I do this in UWP without issue. I don't know of a Windows Store App differing in any way.
private string _informativeMessage;
public string InformativeMessage
{
get { return _informativeMessage; }
set
{
if (_informativeMessage != value)
{
if(value == SomeValueThatMakesMeNeedToChangeIt)
{
_informativeMessage = "Overridden Value";
}
else
{
_informativeMessage = value;
}
RaisePropertyChanged();
}
}
}
What does your property look like?
Related
I'm trying to set a default value into a combo box when the application is first loading using the MVVM pattern and it looks like this is all the time unset, combo box being all the time empty when the page loads.
This is my xaml:
<ComboBox Grid.Row="0" Margin="10,0,0,0" Grid.Column="1"
SelectedItem="{Binding Path=JuiceOperations.SelectedItemOption, Mode=TwoWay}"
SelectedIndex="{Binding Path=JuiceOperations.SelectedComboBoxOptionIndex, Mode=TwoWay}"
SelectedValue="{Binding Path=JuiceOperations.SelectedComboBoxOptionIndex, Mode=TwoWay}"
ItemsSource="{Binding Path=JuiceOperations.JuiceOptions}" />
This is the view model code, with its default constructor:
public JuiceViewModel()
{
juiceOperations.SelectedComboBoxOptionIndex = 0;
juiceOperations.SelectedItemOption = "Cola";
}
where I am trying to set the default value of the combo box.
And this is how the properties looks like:
private List<string> juiceOptions = new List<string> { "Cola", "Sprite", "Fanta", "Pepsi" };
private string selectedItemOption = string.Empty;
private int selectedComboBoxOptionIndex = 0;
public int SelectedComboBoxOptionIndex
{
get
{
return this.selectedComboBoxOptionIndex;
}
set
{
this.selectedComboBoxOptionIndex = value;
this.OnPropertyChanged("SelectedComboBoxOptionIndex");
}
}
public List<string> JuiceOptions
{
get
{
return this.juiceOptions;
}
set
{
this.juiceOptions = value;
}
}
public string SelectedItemOption
{
get
{
return this.selectedItemOption;
}
set
{
this.selectedItemOption = value;
this.OnPropertyChanged("SelectedItemOption");
}
}
When selecting an item from combo box the selection is updated into the model and also into the view, so it is working as expected but when the page is first loaded even if the "SelectedComboBoxOptionIndex" and "SelectedItemOption" are being called and their value updated the view of the page is not updated and the empty string is being shown into the combo box where I was expected to see the "Cola" value, instead of the empty string.
Can someone explain me what I am doing wrong and how should I set the default "Cola" value into the combo box ?
Only bind the SelectedItem property of the ComboBox to the SelectedItemOption source property and set the latter to the string "Cola" in the view model. This should work:
<ComboBox Grid.Row="0" Margin="10,0,0,0" Grid.Column="1"
SelectedItem="{Binding Path=JuiceOperations.SelectedItemOption}"
ItemsSource="{Binding Path=JuiceOperations.JuiceOptions}" />
public JuiceViewModel()
{
juiceOperations.SelectedItemOption = "Cola";
}
Don't mix SelectedItem, SelectedIndex and SelectedValue. You only need one.
mm8 above absolutely right, that should fix your issue.
On a side note, what you have there will work for a static selection list, but consider using an ObservableCollection<string> instead of a List<string>. The former implements INotifyCollectionChanged, which allows the view to be notified if there has been a change in the collection. When you bind an Observable Collection to the view, the view automatically subscribes to the CollectionChanged event. You will need this if you ever need to add or remove options at run time. Side note, OnCollectionChanged will not fire if you simply modify an item, for that you would still need to callOnPropertyChanged("JuiceOptions") in the setter.
something like this (with the appropriate private backing field):
public ObservableCollection<string> JuiceOptions
{
get
{
return this.juiceOptions;
}
set
{
this.juiceOptions = value;
this.OnPropertyChanged("JuiceOptions");
}
}
The value of juiceOperations.SelectedItemOption, that is, "Cola", is not the same "Cola" stored in the ItemsSource. You would need to do something like juiceOperations.SelectedItemOption = juiceOperations.JuiceOptions.First().
In my app I have a user control with the following XAML segment
<StackPanel x:Name="stackPanel" Style="{StaticResource sPanel1}" >
<ToggleButton Style="{StaticResource tButton}">
<TextBlock Text="{Binding Note, Mode=TwoWay}" Style="{StaticResource textBlockStyle}"/>
</ToggleButton>
</StackPanel>
that 'Note' bound in the TextBlock is defined in my model as so:
private string m_Note;
public string Note
{
get { return m_Note; }
set
{
m_Note = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("m_Note"));
}
}
The 'Note' property updates when an event handler from my user control code-behind fires the event:
public void cBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
cm.Note = cBox.SelectedItem.ToString();
}
But every time I select an item from the ComboBox the UI does not update. I know that the binding is correct because when I initialize 'Note' in the model's constructor it does show it's value in the UI, and I know that 'Note' gets the cBox.SelectedItem value because I've walked through the code. What am I missing?
The binding path and mode in the View is correct. That is why you get the value on initialize. Your bound property however is not raising the correct Property Name on property changed. The UI is listening for Note but you are raising m_Note.
You need to update to
private string m_Note;
public string Note
{
get { return m_Note; }
set
{
m_Note = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Note"));
}
}
In WPF with MVVM it's easy to fire some code when the user changes the tab.
<TabControl Margin="0 5 5 5" Background="#66F9F9F9" SelectedIndex="{Binding TabIndex}">
And then in the ViewModel:
private int _tabIndex;
public int TabIndex
{
get { return _tabIndex; }
set
{
if(_tabIndex != value)
{
_tabIndex = value;
OnPropertyChanged("TabIndex");
if(value == 1)
{
//do something
}
}
}
}
But I'm vaguely uncomfortable with this. What if another developer happens along later and adds another tab in the "1" position. If this is application-critical code (which it is), things will break spectacularly.
Danger can be minimized with unit tests, of course. But it made me wonder: is this seen as bad practice? And is there a way of doing this that allows you to refer to the Tab with a string, rather than an int? I tried noodling with binding to the SelectedValue property, but nothing seemed to happen when the tabs were changed.
You could make a behavior for TabItem, listening for changes to the IsSelected dependency property, and raises a Command when the tab is selected. This can be extended to any number of tabs, each which invokes different commands in the viewmodel. You could also supply a command parameter for any optional context:
class TabSelectedBehavior : Behavior<TabItem>
{
public static readonly DependencyProperty SelectedCommandProperty = DependencyProperty.Register("SelectedCommand", typeof(ICommand), typeof(TabSelectedBehavior));
public ICommand SelectedCommand
{
get { return (ICommand)GetValue(SelectedCommandProperty); }
set { SetValue(SelectedCommandProperty, value); }
}
private EventHandler _selectedHandler;
protected override void OnAttached()
{
DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(TabItem.IsSelectedProperty, typeof(TabItem));
if (dpd != null)
{
_selectedHandler = new EventHandler(AssociatedObject_SelectedChanged);
dpd.AddValueChanged(AssociatedObject, _selectedHandler);
}
base.OnAttached();
}
protected override void OnDetaching()
{
DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(TabItem.IsSelectedProperty, typeof(TabItem));
if (dpd != null && _selectedHandler != null)
{
dpd.RemoveValueChanged(AssociatedObject, _selectedHandler);
}
base.OnDetaching();
}
void AssociatedObject_SelectedChanged(object sender, EventArgs e)
{
if (AssociatedObject.IsSelected)
{
if (SelectedCommand != null)
{
SelectedCommand.Execute(null);
}
}
}
}
XAML
<TabControl>
<TabItem Header="TabItem1">
<i:Interaction.Behaviors>
<local:TabSelectedBehavior SelectedCommand="{Binding TabSelectedCommand}"/>
</i:Interaction.Behaviors>
</TabItem>
<TabItem Header="TabItem2">
</TabItem>
</TabControl>
In a similar fashion you could also make a behavior for the TabControl, which turns the SelectionChanged event into a command, and pass the Tag object of the selected TabItem as command parameter.
As with all collection controls, the best way to maintain the selected item is to use the SelectedItem property. If you data bind a property of the relevant data type to the TabControl.SelectedItem property, then you'll still be able to tell which tab is selected and select a different one from the view model.
The only problem with this method is that you'll also need to use the TabControl.ItemsSource property to set up the TabItems:
<TabControl ItemsSource="{Binding YourDataItems}" SelectedItem="{Binding YourItem}" />
If you want to try this, then you should know that defining the TabItems can be a little bit confusing. Please refer to the answer from the How to bind items of a TabControl to an observable collection in wpf? question for help with that.
I am trying to get the get the sum to update in the textblock, however I'm only able to get it updated through restarting the windows phone emulator. Why is it so?
Code in DisplayBill.xaml
<TextBlock x:Name="lbTotalAmt" Text="{Binding Path=Sum, Mode=TwoWay, UpdateSourceTrigger=Explicit}" Margin="0,364,0,10" Grid.Row="1" />
Code in ViewModel.cs
private string _Sum;
public string Sum
{
get {return _Sum;}
set
{
_Sum = value;
NotifyPropertyChanged("Sum");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
// Used to notify Silverlight that a property has changed.
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
if (propertyName == "ToDoBills")
UpdateSumValue();
}
private void UpdateSumValue()
{
Sum = ToDoBills.Sum(i => i.Amount).ToString();
}
#endregion
Update
What I'm trying to do is to update the textblock everytime the listbox adds an item. so everytime a new item is added into the listbox, the textblock which display the total amount will update. So my question is how do I go about updating my textblock everytime a new item is added into the listbox? Can anyone help me please? I tried using the binding expression below but to no avail
public DetailPageBill()
{
InitializeComponent();
// Set the page DataContext property to the ViewModel.
this.DataContext = App.todoViewModel;
BindingExpression be = lbTotalAmt.GetBindingExpression(TextBlock.TextProperty);
be.UpdateSource();
}
Try setting UpdateSourceTrigger to PropertyChanged for your TextBlock's binding:
<TextBlock x:Name="lbTotalAmt" Text="{Binding Path=Sum, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="0,364,0,10" Grid.Row="1" />
With Explicit no automatic update is performed. MSDN says:
Updates the binding source only when you call the
UpdateSource method.
See MSDN on UpdateSourceTrigger for more information.
I have this ToggleButton in my WP7 app which I bind to a property in my ViewModel. I also have a command to the ToggleButton which does work when clicking the button.
Based on the result of that command, I set the property that is bound to the ToggleButton.IsChecked property. But no matter what I set the property to, the toggle button lives its own life and just switches between unchecked and checked. Is this expected behaviour or is this a bug?
It seems like the toggle button loses its binding when clicking on it, would this be true? The reason I want it bound is that I do not always want to change the checked state, because the logic in my command can fail, e.g. network is down so it cant set what I want in the back end, and so forth.
Any workaround for this problem?
Xaml:
<ToggleButton x:Name="ToggleButton" Style="{StaticResource ToggleButtonStyle}" IsChecked="{Binding IsToggleButtonChecked}, Mode=OneWay}" Command="{Binding ToggleButtonCommand, Mode=OneWay}" CommandParameter="{Binding ToggleButtonCommandParameter}"/>
The style sets the image of the button based on states. The command does logic when the button is clicked and, as said earlier, sets IsToggleButtonChecked to desired value. I have both tried OneWay and TwoWay on the IsChecked, but I canĀ“t see the difference.
ViewModel:
public const string IsToggleButtonCheckedPropertyName = "IsToggleButtonChecked";
private bool _isToggleButtonChecked;
public bool IsToggleButtonChecked
{
get { return _isToggleButtonChecked; }
set
{
if (_isToggleButtonChecked == value)
{
return;
}
_isToggleButtonChecked = value;
RaisePropertyChanged(IsToggleButtonCheckedPropertyName);
}
}
This property is set each time i want to change the checked state of the ToggleButton.
Make sure the ToggleButton is being notified of any changes you make to the bound property.
XAML
<ToggleButton Click="OnClicked"
IsChecked="{Binding IsChecked, Mode=TwoWay}" />
C#
private bool _isChecked = false;
public bool IsChecked
{
get { return _isChecked; }
set
{
if( value != _isChecked ) {
_isChecked = value;
NotifyPropertyChanged( "IsChecked" );
}
}
}
Have your logic set IsChecked = false; in code behind to uncheck the button.
Without seeing any code, my first instinct would be to verify that the ViewModel implements INotifyPropertyChanged, and that the setter of the property that is bound to IsEnabled is firing the property changed event when it's set.
using System.ComponentModel;
class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool _enableCheckBox;
public bool EnableCheckBox
{
get { return _enableCheckBox }
set
{
_enableCheckBox = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("EnableCheckBox"));
}
}
}
For others that might wonder about the same: I solved this by using TwoWay mode as Praetorian said, but let it change its value by itself for the normal scenarios. The times I want it to stay in the same state as before I clicked it, I just set the bindable value to the wanted value. Also, I have another variable that keeps track of the isChecked state that is not binded. By doing that, I can check and set the value accordingly, and don't mess up the visual checked state. This works, but its not a perfect way to do it.