0

I have a simple WPF textbox that I want to update once when the window is initialized, and then each time a certain event occurs. I have followed the instructions in many StackOverflow answers, in particular this one: WPF: simple TextBox data binding So I came up with the following.

In xaml:

<TextBox x:Name="txtFeedback" 
         TextWrapping="Wrap" 
         Text="{Binding Path=FeedbackText, Mode=TwoWay,
                        UpdateSourceTrigger=PropertyChanged}" 
         IsReadOnly="True" 
         AcceptsReturn="True"/>

And in the MainWindow:

private string _feedbackText;
public event PropertyChangedEventHandler PropertyChanged;

public string FeedbackText
{
    get
    {
        return _feedbackText;
    }
    set
    {
        _feedbackText = value;
        OnPropertyChanged("FeedbackText");
    }
}

protected void OnPropertyChanged(string propertyName)
{
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

public MainWindow()
{
    InitializeComponent();
    this.DataContext = this;
    FeedbackText = "Awaiting start of process...";
}
private void FinishedWorksheet(object sender, EventArgs e)
{
    FeedbackText += "Done another worksheet" ;
}

When the constructor of the form is called, the textbox correctly displays "Awaiting start of process...", but when I call the FinishedWorksheet method (which is called by some other event) the textbox is not updated.

Please note that if I put breakpoints inside OnPropertyChanged I can see that it is called, and in the immediate window I can see the Text property actually changing, but somehow the textbox is not updated.

txtFeedback.Text

"Awaiting start of process...Done another worksheet"

Update

The problem was much simpler, and sorry to everyone for not posting one fundamental part of the code: I was doing everything inside the GUI thread! So of course the GUI could not react until I released the thread! Once I made the external call Asynchronous, it all works fine. Thank you all for your suggestions though, I learned a lot, and I hope others did too.

AlePorro
  • 111
  • 1
  • 11
  • Why is it set `IsReadOnly="True"`? – Pavel Anikhouski Aug 06 '19 at 12:20
  • I want it to be used only for feedback: the user should not be able to change it. I tried removing it but it doesn't make a difference – AlePorro Aug 06 '19 at 12:26
  • It could be multithreading issue. What event is that handler? Try to dispatch the call inside event handler: `Dispatcher.Invoke(() => { ... });` – Sinatr Aug 06 '19 at 12:29
  • This is a very strange way of updating your view. INotifyPropertyChanged is usually implemented in a view model, not code behind. If you're in code behind you can just update the textbox directly. – GazTheDestroyer Aug 06 '19 at 13:12
  • @GazTheDestroyer yes I am new to WPF and I realized that the correct way to do this is using a view model. Yet, regarding your point to just update the textbox directly, I did initially but the change was not being displayed, that's why I got into all of this mess. – AlePorro Aug 06 '19 at 13:23
  • @AlePorro Do you have `public partial class MainWindow : Window, INotifyPropertyChanged` in your MainWindow? As in the following solution? – NetCoreDev Aug 06 '19 at 13:36
  • @NetCoreDev yes I do. – AlePorro Aug 06 '19 at 14:00
  • @AlePorro How do you call the `FinishedWorksheet` function? Show how it looks in XAML – NetCoreDev Aug 06 '19 at 14:03
  • @AlePorro If your TextBox is not updating when you set the control text directly from code behind, then you have a more fundamental problem and all this binding stuff is a red herring. Have you tried a non-readonly TextBox? Are you setting the control text directly elsewhere? (which would also destroy your binding BTW). Is there more than one TextBox? – GazTheDestroyer Aug 07 '19 at 10:36

3 Answers3

-1

Do it without information of bidirectional binding

<TextBox x:Name="txtFeedback" HorizontalAlignment="Left" Height="213" Margin="10,196,0,0" TextWrapping="Wrap" Text="{Binding FeedbackText}" VerticalAlignment="Top" Width="772" IsReadOnly="True" AcceptsReturn="True"/>

Change += to =

private void FinishedWorksheet(object sender, EventArgs e)
{
    FeedbackText = "Done another worksheet" ;
}

MainWindow:

public partial class MainWindow : Window, INotifyPropertyChanged
{
    private string _feedbackText;
    public event PropertyChangedEventHandler PropertyChanged;

    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = this;
        FeedbackText = "Awaiting start of process...";
    }

    public string FeedbackText
    {
        get
        {
            return _feedbackText;
        }
        set
        {
            _feedbackText = value;
            OnPropertyChanged("FeedbackText");
        }

    }
    protected void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    private void FinishedWorksheet(object sender, EventArgs e)
    {
        FeedbackText = "Done another worksheet";
    }
}

I do not know how you call FinishedWorksheet. But if this is the method assigned to Button, it should work.

NetCoreDev
  • 325
  • 1
  • 8
  • I am afraid this won't happen. This is always a missed chance to learn from the wrongs. – BionicCode Aug 06 '19 at 13:02
  • My point is that controls should use `DependencyProperty`. Also `stringA += stringB` evaluates to `stringA = stringA + stringB`. So there is no difference. The property will be set in either way. – BionicCode Aug 06 '19 at 13:09
  • DependencyProperty is an add-on that can be used for some reasons. It has several advantages but it is not always necessary. In this case, my task is an exaggeration and on the other hand, it will not have an impact on action in this situation – NetCoreDev Aug 06 '19 at 13:17
  • When we use viewmodel to bind data to our controls, we need not use dependency properties. But, we are trying to bind the controls to the values in code behind, so it cannot work without dependency property. – Lingam Aug 07 '19 at 04:07
  • @Lingam The solution works for me successfully. I don't have to use `DependecyProperty` (as given in another answer) for it to work – NetCoreDev Aug 07 '19 at 07:14
  • @Lingam MVVM should be used in this case. The fact that it is not done because it does not make sense does not change the fact that it works as I added – NetCoreDev Aug 07 '19 at 07:17
-1

Remove IsReadOnly and binding Enabled in Model instead. I think it solves your problem

<TextBox x:Name="txtFeedback" HorizontalAlignment="Left" Height="213" Margin="10,196,0,0" TextWrapping="Wrap" Text="{Binding Path=FeedbackText}" VerticalAlignment="Top" Width="772" Enabled="{Binding Path=IsEnabled}" AcceptsReturn="True"/>

public bool IsEnabled
{
    get
    {
        return _isEnabled;
    }
    set
    {
        _isEnabled= value;
        OnPropertyChanged("IsEnabled");
    }
}
Antoine V
  • 6,998
  • 2
  • 11
  • 34
-1

On controls always use DependencyProperty. Then change the binding too (try to avoid to set the DataContext of a Control to this):

public static readonly DependencyProperty FeedbackTextProperty = DependencyProperty.Register(
  "FeedbackText",
  typeof(string),
  typeof(MainWindow),
  new PropertyMetadata(default(string)));

public string FeedbackText
{
  get => (string) GetValue(MainWindow.FeedbackTextProperty);
  set => SetValue(MainWindow.FeedbackTextProperty, value);
}

Also because the TextBox is for display only consider to use a TextBlock instead:

<TextBlock x:Name="TxtFeedback" 
           TextWrapping="Wrap" 
           Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=MainWindow}, Path=FeedbackText}" />

Remarks
The Binding.Mode of the TextBox.Text property is TwoWay by default. A read-only TextBox doesn't require TwoWay binding at all since it can't receive input.
Also always avoid string literals. Instead use nameof({Property}) e.g. OnPropertyChanged(mameof(this.FeedbackText);. This will also make refactoring easier and is safe.

BionicCode
  • 1
  • 4
  • 28
  • 44
  • Thanks @BionicCode, I'm trying to implement your code and I'll let you know how it goes. Just regarding your remarks though, I believe the default value for `UpdateSourceTrigger` of a textbox is `LostFocus`. [link](https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/how-to-control-when-the-textbox-text-updates-the-source) – AlePorro Aug 06 '19 at 13:11
  • 1
    Yes, you are right. Default is `LostFocus`. Last tip: replace string literals with `nameof()` e.g. `OnPropertyChanged(nameof(this.FeedbackText)`. This is not error prone like string literals and makes code refactoring easier. – BionicCode Aug 06 '19 at 13:15