PropertyChanged not updating UI in user control - universal app - c#

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"));
}
}

Related

Change property from setter and notify

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?

Data binding to singleton class

I've searched here a number of times and found a bunch of examples, but can't seem to get anything to work.
I've got a solution set up where a ViewModel refers to a MainViewModel class through a locator. The main view model class has:
public NotifyLog Log
{
get { return LogMgr.Instance.Log; }
}
In it. This allows me to specify:
<TextBox IsEnabled="True" Text="{Binding Log.Text, Mode=OneWay}" />
The NotifyLog is defined as:
public class NotifyLog : INotifyPropertyChanged
{
public NotifyLog()
{
_Log = new StringBuilder();
}
private void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
public event PropertyChangedEventHandler PropertyChanged;
private StringBuilder _Log;
public void Append(String Value)
{
_Log.Append(Value);
OnPropertyChanged("Text");
}
public string Text
{
get { return _Log.ToString(); }
}
public override string ToString()
{
return this.Text;
}
}
For the initial start of the application, the text box is populated but, the OnPropertyChanged handler is never automatically populated by the binding so no changes are detected. I'm doing something wrong, I just don't know what...
Thanks for your time,
BlD
if you want to update the log when typing in the text box you need to change the binding mode to TwoWay. also the event is triggered when you exit from the text box, not on each char typed.
if you want to update the text box when the log is changed you need to add a setter to the Text property and raise the NotifyPropertyChanged event (in the setter).
also check the output of the program for some binding errors.
To the line:
<TextBox IsEnabled="True" Text="{Binding Log.Text, Mode=OneWay}" />
Try adding the "UpdatedSourceTrigger" like so:
<TextBox IsEnabled="True" Text="{Binding Log.Text, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />

WPF TextBox won't update to programmatic change in ViewModel

I have a WPF ViewModel
class MainWindowViewModel : INotifyPropertyChanged
{
private string _sql;
public string Sql
{
get { return _sql; }
set
{
if (value == _sql) return;
OnPropertyChanged("Sql");
_sql = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
I also have a XAML view with a TextBox
<Window.Resources>
<HbmSchemaExporter:MainWindowViewModel x:Key="viewModel"/>
</Window.Resources>
....
<TextBox Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="2" Text="{Binding Source={StaticResource ResourceKey=viewModel}, Path=Sql,Mode=OneWay}"/>
Code behind
private MainWindowViewModel ViewModel
{
get { return Resources["viewModel"] as MainWindowViewModel; }
}
The problem is that when in the code I do viewModel.Sql = SOMETHING the text box doesn't get updated. Debugger displays the correct value in the property but the textbox remains blank.
I also tried to change the binding to TwoWay but that only allows me to overwrite the property with a value I type in the textbox, which is something I don't really want (actually I still need to make it readonly, but it's currently out of scope).
How can I update the textbox after programmatically updating the property?
The application is basically a NHibernate DDL generator I'm writing after reading this. I need to press a "Generate SQL" button and it displays the code to run onto DB.
public string Sql
{
get { return _sql; }
set
{
if (value == _sql) return;
OnPropertyChanged("Sql");
_sql = value;
}
}
That does not make sense. At the point that any PropertyChanged event handler is called, reading Sql will still give the old value, because you haven't updated _sql yet. You need to first update the value, and only then raise the PropertyChanged event.

Data binding to textblock does not update

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.

Update binding of property in silverlight

I'm trying to update a property using a PropertyChangedEventHandler, but I think my conceptual understanding of how this works might be a bit flawed. As I'm new to WPF and silver-light.
So, let me explain, I have a property that is set to 0, but after some time a thread changes the value from 0 to 9 internally, but despite the change in value, this property never gets updated in the actual view and I don't know why! Even after I implement a PropertyChangedEventHandler there is no change, but if I log the property it shows that the value is in fact 9
So here is the snippet of code that implements PropertyChangedEventHandler:
public class CustomColumn : IColumnViewable, INotifyPropertyChanged
{
...
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public void OnPropertyChanged(string propertyName)
{
Foo.log.Error(": start on property change");
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
Foo.log.Error(": end on property change");
}
public static string _total;
public string total { get { return _total; } set { _total = value; OnPropertyChanged("total"); Foo.log.Error(": property change"); } }
...
}
Here is part of my xaml:
<DataTemplate x:Key="ColumnView">
<UserControl HorizontalAlignment="Stretch">
<StackPanel HorizontalAlignment="Stretch">
...
<RichTextBox Margin="5,2,5,2">
<Paragraph>
<Run Text="{Binding Path=total, Mode=OneWay}" FontWeight="Bold" FontSize="30" />
<Run Text=" total clicks" FontWeight="Bold" />
</Paragraph>
</RichTextBox>
...
<ContentControl VerticalAlignment="Stretch" Content="{Binding Path=timeline}" ContentTemplate="{Binding Path=timelineView.ContentTemplate}" />
</StackPanel>
</UserControl>
</DataTemplate>
And I do this on initialize:
CustomColumn content = new CustomColumn();
content.total = "0";
And then I pass the object to a thread which at some point does this:
content.total = "9";
Foo.log.Error("value is "+content.total);
And the property never updates and I don't know why - any help is greatly appreciated
If I understand the details of your question, you are updating a UI bound value on a background thread. You need to make that happen on the UI thread or the change will not be visible. In one of our WPF apps random updates were disappearing until we realised this.
We do a lot of multi-threading in our Silverlight (and WPF) apps so to avoid this problem, we implemented our notify helper in a base class like the one below (other stuff trimmed out). It dispatches all notify messages on the main UI thread. Give it a try:
public class ViewModelBase : INotifyPropertyChanged
{
protected delegate void OnUiThreadDelegate();
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void SendPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
// Ensure property change is on the UI thread
this.OnUiThread(() => this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)));
}
}
protected void OnUiThread(OnUiThreadDelegate onUiThreadDelegate)
{
// Are we on the Dispatcher thread ?
if (Deployment.Current.Dispatcher.CheckAccess())
{
onUiThreadDelegate();
}
else
{
// We are not on the UI Dispatcher thread so invoke the call on it.
Deployment.Current.Dispatcher.BeginInvoke(onUiThreadDelegate);
}
}
}
Your code does not show where you make the object the DataContext of your controls, which is necessary for your bindings which do not specify another source and hence bind to the DataContext.
CustomColumn content = new CustomColumn();
content.total = "0";
Do you have any line after this where this object is passed to your view?

Categories