0

I have TextBox in XAML that appears to be bound correctly and the UI will correctly update when the datasource is changed. I'm attempting to do this in the "proper WPF way" as recommended by several articles. The problem I'm having is that the UI thread is continuing to do work and the UI will not update immediately despite executing the datasource update on a separate thread.

//XAML

<Window x:Class="WPFDataBindingTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPFDataBindingTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="480" Width="759">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="900*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="380"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid Grid.Row="0">
            <TextBox Name="tbFileTransferLog" Grid.Row="0" Grid.Column="0" FontSize="15" Width="740" Height="360" HorizontalAlignment="Left" TextWrapping="Wrap" Margin="5" AcceptsReturn="True" VerticalScrollBarVisibility="Visible"/>
        </Grid>
        <Button Grid.Column="0" Grid.Row="1" Width="200" Height="40" Click="UpdateTextboxButton_Click">Update Textbox</Button>
    </Grid>
</Window>


//C#

public class TextBlockUpdate : INotifyPropertyChanged
{
    private string _tbFileTransferLogText;
    // Declare the event

    public string tbFileTransferLogText
    {
        get { return _tbFileTransferLogText; }
        set
        {
            _tbFileTransferLogText = value;
            // Call OnPropertyChanged whenever the property is updated
            OnPropertyChanged();
        }
    }

    // Create the OnPropertyChanged method to raise the event
    // The calling member's name will be used as the parameter.
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName] string name = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    }
}

        
public MainWindow()
{
    InitializeComponent();
}

public void UpdateTextboxButton_Click(object sender, RoutedEventArgs e)
{
    TextBlockUpdate textBlockUpdate = new TextBlockUpdate();

    Binding nameBindingObject = new Binding("tbFileTransferLogText");

    // Configure the binding
    nameBindingObject.Mode = BindingMode.OneWay;
    nameBindingObject.Source = textBlockUpdate;
    //nameBindingObject.Converter = NameConverter.Instance;
    nameBindingObject.ConverterCulture = new CultureInfo("en-US");
    nameBindingObject.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
    // Set the binding to a target object. The TextBlock.Name property on the NameBlock UI element
    BindingOperations.SetBinding(tbFileTransferLog, TextBox.TextProperty, nameBindingObject);

    //tbFileTransferLog.Dispatcher.BeginInvoke(new Action(() => textBlockUpdate.tbFileTransferLogText = "text is updated" + Environment.NewLine));
    //Task.Factory.StartNew(() => { textBlockUpdate.tbFileTransferLogText = "text is updated"; });

    new Thread(() =>
    {
        Thread.CurrentThread.IsBackground = true;
        /* run your code here */
        textBlockUpdate.tbFileTransferLogText = "text is updated";
    }).Start();
            
    Thread.Sleep(10000);
}


What I noticed (using debug mode) is that both the setter and OnPropertyChanged() are executed immediately when the button is clicked, but the UI is not updated until later when Sleep() completes.

What am I missing?

Thanks.

  • You are blocking the UI thread when you do `Thread.Sleep(10000);`. You are preventing the UI from updating. – Ilian Jan 25 '23 at 03:37
  • Thread.Sleep(10000) is just to simulate the real work that is being done in the application which is WinScp file transfers, this code is just an extract from a larger app to try to understand the problem. From all the articles I read, and there were many, the solution to this was to update the textbox in a separate thread. if this is not the solution then what would be? What I'm trying to achieve is a large number of file transfers using the winscp wrapper but to update the UI after each transfer. – user3259859 Jan 25 '23 at 04:00
  • The model that I've attempted to use here is based on what I have gleaned from other postings attempting to solve the same problem, which is to be able to update the UI during a long running thread – user3259859 Jan 25 '23 at 04:11
  • If you want to simulate work, don't use Thread.Sleep(). When you are blocking the UI thread, it's frozen so it can't update the UI. Try `await Task.Delay(10000)` instead which doesn't block. You will have to change the signature of the method to `async void` though. – Ilian Jan 25 '23 at 04:25
  • In my real app the winscp file transfers aren't in an await task, and I think that would be complicated to do, hence I'm using sleep() in this way. Perhaps I'm misunderstanding, but isn't the purpose of executing the update of the datasource in a separate thread so that the UI will update asynchronously? If I put my file transfers (the actual work) in a separate task/thread then there would be no need to go to this extent to update the UI in a separate thread as well? – user3259859 Jan 25 '23 at 05:00
  • In your real app, if you are updating from the background thread. The UI should update immediately even without async/await. The problem is JUST with your simulation using Thread.Sleep(). For example, put the Thread.Sleep() after ` textBlockUpdate.tbFileTransferLogText = "text is updated";` within the other thread. It should update the UI immediately. Because that way, you are not blocking the UI thread. – Ilian Jan 25 '23 at 05:06
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/251379/discussion-between-user3259859-and-ilian). – user3259859 Jan 25 '23 at 05:31
  • I've posted some more discussion in the chat, but just to clarify here. If I move the sleep to the background thread as you suggested it seems I am defeating the purpose of updating the UI in the background. Of all the articles that I read on this topic my understanding is that the GUI update can be done in the background and the main thread continues the long running work. What you are suggesting (it seems to me) is that I move my long running tasks to a background thread as well; if I do this and my main/gui thread is not blocked then why would I need to move the gui updates to background? – user3259859 Jan 25 '23 at 06:00
  • You also need to be using the Dispatcher to update the textbox in the background thread. Are you familiar with that? You won't get that from the supposedly duplicate question. – Emperor Eto Jan 25 '23 at 12:04
  • @PeterMoore, WPF automatically marshalls INotifyPropertyChanged to the UI thread. It's only collection events that need to be on the UI thread. He doesn't need to use a Dispathcher in this case. – Ilian Jan 26 '23 at 08:07
  • @ilian is that new behavior? Otherwise I guess I've been doing this unnecessarily for 15 years! – Emperor Eto Jan 27 '23 at 16:40
  • 1
    @PeterMoore Can't remember when. But should be true for at least .NET Framework 3.5. See this for example: https://stackoverflow.com/questions/1321423/does-wpf-databinding-marshall-changes-to-the-ui-thread – Ilian Jan 28 '23 at 10:29
  • I've ended up moving the worker to another thread, which in the end was the proper way to do it anyway, and now the UI is being updated immediately. What I'm still not clear on was my simulation of Thread.Sleep() blocking the UI thread. My understanding is still that if I do the UI update on a separate thread (such as with BegineInvoke) then it doesn't matter that the long running work is being done in the main/UI thread. I thought that the whole idea of putting the UI updates in a separate thread was so they wouldn't be blocked by whatever long running task is running in the main thread. – user3259859 Feb 01 '23 at 04:38

0 Answers0