By calling Threading.Thread.Sleep 300 in the user interface thread, you block the Windows message pump and prevent any updates from occurring in this thread.
The easiest way to handle this is to move everything to the async workflow and perform the update in the background thread. However, you will need to update the user interface in the main thread. async workflow, you can switch directly between each other.
This requires a few small changes:
#r "WindowsBase.dll"
Note that this is also possible with data binding. To bind (and update it), you need to bind to a "view model" - some type that implements INotifyPropertyChanged , and then creates a binding (which is ugly in code). The problem with the user interface thread is somewhat simplified - you still need to push work away from the user interface thread, but when binding to a simple property, you can set the value for other threads. (If you are using a collection, you still need to switch to the UI thread.)
An example converted to using binding would be something like this:
#r "WindowsBase.dll" #r "PresentationCore.dll" #r "PresentationFramework.dll" #r "System.Xaml.dll" open System open System.Windows open System.Windows.Controls open System.Windows.Data open System.ComponentModel type TextWrapper (initial : string) = let mutable value = initial let evt = Event<_,_>() member this.Value with get() = value and set(v) = if v <> value then value <- v evt.Trigger(this, PropertyChangedEventArgs("Value")) interface INotifyPropertyChanged with [<CLIEvent>] member __.PropertyChanged = evt.Publish [< STAThread >] do let textBlock = TextBlock() // Create a text wrapper and bind to it let text = TextWrapper "Drag and drop a folder here" textBlock.SetBinding(TextBlock.TextProperty, Binding("Value")) |> ignore textBlock.DataContext <- text let getFiles path = async { for file in IO.Directory.EnumerateFiles path do text.Value <- text.Value + "\r\n" + file // how to make this update show in the UI immediatly? // do some slow file processing here.. this will happen on a background thread Threading.Thread.Sleep 300 // just a placeholder to simulate the delay of file processing } |> Async.Start let w = Window() w.Content <- textBlock w.Title <- "UI test" w.AllowDrop <- true w.Drop.Add(fun e -> if e.Data.GetDataPresent DataFormats.FileDrop then e.Data.GetData DataFormats.FileDrop :?> string [] |> Seq.iter getFiles) let app = Application() app.Run(w) |> ignore
Note that this can be simplified if you want to use something like FSharp.ViewModule (makes creating the INotifyPropertyChanged part much nicer).
Edit:
The same script can be executed using XAML and FSharp.ViewModule and simplify its extension later. If you use paket to refer to FSharp.ViewModule.Core and FsXaml.Wpf (latest version), you can transfer the user interface definition to a XAML file (assuming the name MyWindow.xaml ), for example:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" Title="UI Test" AllowDrop="True" Width="500" Height="300" Drop="DoDrop"> <ScrollViewer > <TextBlock Text="{Binding Text}" /> </ScrollViewer> </Window>
Note that I have “improved” the user interface here - it wraps the text block in the scroll viewer, sets the size, and declares the binding and event handler in XAML instead of code. You can easily expand this with more bindings, styles, etc.
If you place this file in the same place as your script, you can write:
#r "WindowsBase.dll" #r "PresentationCore.dll" #r "PresentationFramework.dll" #r "System.Xaml.dll" #r "../packages/FSharp.ViewModule.Core/lib/portable-net45+netcore45+wpa81+wp8+MonoAndroid1+MonoTouch1/FSharp.ViewModule.dll" #r "../packages/FsXaml.Wpf/lib/net45/FsXaml.Wpf.dll" #r "../packages/FsXaml.Wpf/lib/net45/FsXaml.Wpf.TypeProvider.dll" open System open System.Windows open System.Windows.Controls open System.Windows.Data open System.ComponentModel open ViewModule open ViewModule.FSharp open FsXaml type MyViewModel (initial : string) as self = inherit ViewModelBase()
Note that this works by creating a “view model” for the binding. I moved the logic to a ViewModel (which is shared), and then use FsXaml to create a window from Xaml, and vm used as the DataContext of the window. This will “bind” any bindings for you.
With one binding, this is more detailed, but as the user interface expands, the benefits become much clearer very quickly, as adding properties is simple, and styling becomes much easier when using XAML and trying to style the code. For example, if you start using collections, it is incredibly difficult to create the right patterns and styles in your code, but trivial in XAML.