Does anyone know how to add an F# event handler to a control in the xaml file?
I just gave a talk about reactive programming in F# (in London) that used Silverlight to implement most of the examples. The talk has been recorded and the samples are available for download as well, so this may be a useful resource:
See my blog with links to source, recording and slide
To answer your specific question, I don't think that you can use the usual style of specifying event handler in the XAML file (this may work in F# Silverlight Application, but you would have to use member instead of let function).
However, the best way (I can think of) for writing Silverlight components is to have just an F# Silverlight Library and use that from a C# Silverlight application. In that case, you need to write the event handler binding in code. A simplified example (from one of the samples from the talk) would look like this:
open System.Windows.Controls
// Dynamic invoke operator that makes accessing XAML elements easy
// (easier than using 'FindName' explicitly in the code
let (?) (this : Control) (prop : string) : 'T = // '
this.FindName(prop) :?> 'T // '
type MyControl() as this =
inherit UserControl()
do
let path = "/MyProject;component/MyControl.xaml"
let uri = new System.Uri(path, UriKind.Relative)
Application.LoadComponent(this, uri)
// Get Button named 'TestButton' from the XAML file
let btn : Button = this?TestButton
// Add event handler to the button
btn.Add(fun _ -> btn.Text <- "Clicked!")
Essentially you need to load a xaml file and find a control by name:
let w = Application.LoadComponent(new System.Uri(
"/twitterstream;component/Window.xaml", System.UriKind.Relative
)) :?> Window
let b = w.FindName("buttonToggle") :?> Button
and then you can simple add a handler an event:
b.Click.Add(fun _ -> ...)
You can get fancy and use first-class event combinators - here is a great step-by-step introduction:
Link
Related
I'm building an application using PRISM and MVVM. I have a view model that needs to display a non-modal dialog box to the user indicating an operation is in progress. I'm using essentially an abstracted IDialogService.
My question is: where should I store the strings for the title and the message shown in this dialog box? The view model's logic causes the dialog box to be displayed and determines when it should be closed. Hence, I have code that looks like this in my view model:
let! closeDlgAction =
dialogSvc.ShowDialogModeless (
"Opening File",
"Please wait while your selected file is opened.") |> Async.AwaitTask
I'm thinking about localization scenarios. WPF has its own mechanism for providing localization through resource dictionary, etc. It seems like these strings belong in a resource dictionary, but the view model shouldn't have a dependency on WPF resource directories - especially because the same view model is going to be used on a Xamarin Forms application later.
The best solution that comes to mind is to use a service that abstracts the resource library away (e.g. IDialogStringService), but I wonder if there's a better or more preferred approach?
You shouldn't use resource dictionaries (xaml) to store text. Instead you have to use Resources (*.resx). In VS:
Right click on project
Add -> New Item...
Find "Resources File" template, type name, and click Add
Opt. Open this file (special editor will opened) and on top bar switch Access Modifier to Public, if you want get access to text from another project or from XAML. Add some key\value strings.
Right click on resource file and click Run Custom Tool. New class will generated with static properties with names based on your keys from Step 4.
How to use (if file has name Localizations.resx and has string with key "AppTitle")
From code:
let! closeDlgAction =
dialogSvc.ShowDialogModeless (
Localizations.AppTitle,
"Please wait while your selected file is opened.") |> Async.AwaitTask
From xaml:
<Window
x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{x:Static Localizations.AppTitle}"/>
*.resx file and *.cs file that is generated both don't depend on any WPF assemblies, so you can use them in different assemblies: in shared view models, from wpf views and from xamarin views. Just put you *.resx file in separate netstandard assembly and refer to it where do you need it from
Cons of this way:
resx generates class with strings and each string is public property, so static code analyze works
You don't have add new abstraction level
You can ref strings from code files or from XAML
I liked Vadim's answer, and I have used that approach before. If my View Models lived in the same project as the WPF project, that would be the best solution.
However, my View Models are in a different library (and a different language) and will be shared between a Prism MVVM WPF project and a Prism MVVM Xamarin Forms project. I could still use resources in the View Model library, but then localization concerns would exist separately in both the WPF project (for the Views) and the View Model library. IMO the localization concern should be centralized.
As such, I decided to abstract the resources behind a service. Implementing the resource service turned out to be more straightforward than I thought. To use an indexer intuitively, I defined a "resource container object" that is returned by IResourceService, as seen below:
public struct ResourceContainer
{
private readonly Func<string, string> _resourceGetter;
public string this[string resourceId] => _resourceGetter(resourceId);
public ResourceContainer(Func<string, string> resourceGetter) => _resourceGetter = resourceGetter;
}
public interface IResourceService
{
ResourceContainer Resources { get; }
}
And the service implementation in the WPF library is as follows:
public class ResourceService : IResourceService
{
public ResourceService()
{
Resources = new ResourceContainer((s) => Application.Current.Resources[s] as string);
}
public ResourceContainer Resources { get; }
}
In the WPF layer's XAML resource directory:
<s:String x:Key="FileOpenDialogTitle">Opening File</s:String>
<s:String x:Key="FileOpenDialogMessage">Please wait while your selected file is opened.</s:String>
And, finally, the View Model consumes this service by requesting IResourceService on its constructor, and is used as follows:
let! closeDlgAction =
dialogSvc.ShowDialogModeless (
resourceSvc.Resources.["FileOpenDialogTitle"],
resourceSvc.Resources.["FileOpenDialogMessage"]) |> Async.AwaitTask
This approach will ultimately require implementing the resources twice - once for the WPF project and once for the XF project, but I have to implement the Views twice, anyway. At least the localization concerns are centralized in both cases (or perhaps a shared resource library can be used between both projects).
EDIT: This technique could also leverage Vadim's suggestion by putting the localization resource (.resx) in the WPF project as well, and either having the XAML resource directory reference the static resources, or have the ResourceService return the resource directly. Having the resources in .resx format may make sharing them between multiple projects more straightforward.
I'm trying to make a Button Hello World application with F#, WPF and FsXaml. I started following this guide:
https://www.c-sharpcorner.com/article/create-wpf-application-with-f-sharp-and-fsxaml/
Everything works fine when I just load things on xaml and compile, but I haven't managed to call a function by pressing a button and the guide ends before he explains how to call functions.
I've seen a lot of different kind of approaches around, but nothing has worked for me yet (and many of the guides are years old so a lot has happened inside frameworks since). It would be great to have a working (and simple) starting point on which I could start building once I understand the logic between x.xaml and x.xaml.fs when using FsXaml.
My button on MainWindow.xaml:
<Button x:Name="submitButton" Content="Send" Click="submitButton_Click"/>
Also I have this in window -section of MainWindow.xaml:
xmlns:local="clr-namespace:Views;assembly=GUItemplate"
My MainWindow.xaml.fs:
namespace GUItemplate
open FsXaml
open System.Windows
type MainWindowBase = XAML<"MainWindow.xaml">
type MainWindow() =
inherit MainWindowBase()
override this.submitButton_Click (sender: obj, e: RoutedEventArgs) =
MessageBox.Show("Hello world!")
|> ignore
The error I get currently:
System.Windows.Markup.XamlParseException
HResult=0x80131501
Message='Failed to create a 'Click' from the text 'submitButton_Click'.' Line number '29' and line position '101'.
Source=PresentationFramework
Inner Exception 1:
ArgumentException: Cannot bind to the target method because its signature or security transparency is not compatible with that of the delegate type.
This is how I proceed in VS 2017 and for me it works.
I add the UIAutomationTypes reference and I install the NuGet FsXaml.Wpf.
open System
open System.Windows
open FsXaml
type MainWindowBase = XAML<"MainWindow.xaml">
type MainWindow() =
inherit MainWindowBase()
override this.submitButton_Click (sender: obj, e: RoutedEventArgs) =
MessageBox.Show("Hello world!")
|> ignore
[<EntryPoint;STAThread>]
let application = new Application() in
let mainWindow = new MainWindow() in
application.Run(mainWindow) |> ignore
I'm writing tests which will check correctness of Binding elements specified in XAML. They work so far, the only issue is that I do not know how to correctly force databinding to happen. Surprisingly it is not enough to simply set something in DataContext, binding won't happen until you show your control/window. Please not that I'm writing 'unit'-tests and I'd like to avoid showing any windows.
Take a look at following code:
// This is main class in console application where I have all WPF references added
public class Program
{
[STAThread]
public static void Main()
{
var view = new Window();
BindingOperations.SetBinding(view, Window.TitleProperty, new Binding("Length"));
view.DataContext = new int[5];
//view.Show(); view.Close(); // <-- this is the code I'm trying not to write
Console.WriteLine(view.Title);
}
}
Here I'm creating a Window and putting an array as DataContext to that window. I'm binding Window.Title to Array.Length so I expect to see number 5 printed in console. But until I Show window (commented line) I will get empty string. If I uncomment that line then I will receive desired 5 in console output.
Is there any way I can make binding happen without showing a window? It is pretty annoying to look at ~20 windows while launching tests.
P.S.: I know I can make windows more transparent and etc, but I'm looking for more elegant solution.
UPDATE Code above is simplified version of what I really have. In real code I receive a View (some UIElement with bindings) and object ViewModel. I do not know which exactly binding there were set on View, but I still want all of them to be initialized.
UPDATE 2: Answering to the questions regarding what I test and I why. I do not intend to test that classes like Binding, BindingBase, etc are working as expected, I assume they are working. I'm trying to test that in all my XAML files I have written bindings correctly. Because bindings are stringly typed things, they are not verified during compilation and by default they cause only errors in output window, which I'm missing occasionally. So if we take my example from above and if we will made a typo there in binding: {Binding Lengthhh} then my tests will notify you that there is no property with name Lengthhh available for binding. So I have around 100 XAML files and for each XAML I have a test (3-5 lines of code) and after launching my tests I know for sure that there are no binding errors in my solution.
The bindings are updated by the dispatcher with the DispatcherPriority.DataBind - so if you wait for a dummy task with SystemIdle priority you are sure that any pending databinding is done.
try
{
this.Dispatcher.Invoke(DispatcherPriority.SystemIdle, new Action(() => { }));
}
catch
{
// Cannot perfom this while Dispatcher in suspended mode
}
If you are trying to test correctness of your view, I suggest you test your view :-)
Why not run the UI from a unit test and write code that checks content of UI after changing data.
VS2010 does have GUI testing, or you could take a look at the code of tools such as Snoop.
Edit following comment:
If ALL you want to do is test a few simple bindings, try writing a static code test that runs as a post build event using reflection on view models and regular expressions on XAMLs. Add attributes on VM or use a config file so your test will know which view receives which View Model as DataContext. Compare property names and types in View Models with binding strings in View (automatically search XAML for these) and throw exception (thus failing build) if strings do not match.
If your bindings are more complex (converters, multibindings, ...) this may be a bit more complicated to implement.
I think you should first set the DataContext and then do the Binding, e.g.:
view.DataContext = new int[5];
BindingOperations.SetBinding(view, Window.TitleProperty, new Binding("Length"));
I'm not sure if this is real solution for your general problem, but it works in this case.
I don't believe the Window's bindings will run without calling Show or ShowDialog, because that is the only way it gets associated with the UI message loop/dispatcher.
Your best bet would be to set it to be as least visible as possible, potentially using an extension method to clean things up:
public static void PokeWindowDispatcher(this Window window)
{
window.WindowState = WindowState.Minimized;
window.ShowInTaskbar = false;
window.Visibility = Visibility.None;
using (var wait = new ManualResetEvent())
{
Action<object, RoutedEventArgs> loaded = (sender, e) => wait.Set();
window.Loaded += loaded;
try
{
window.Show();
wait.WaitOne();
}
finally
{
window.Loaded -= loaded;
window.Close();
}
}
}
I had the same problem, and from sixlettervariables gave me an idea. It's very simple.
I am using WPF in WinForms application, so I use ElementHost control to host Wpf controls on WinForms control. To enforce WinForms control initialization you can just read value of Handle (which is actually Windows HWND) and this will force control to fully initialize itself including child ElementHost and all Wpf binding work.
I didn`t try to perform the same thing for pure Wpf control. But you can easily use ElementHost to initialize your Wpf controls like this:
var el = new ElementHost();
var p = new TextBlock();
p.DataContext = new { Data = "1234" };
p.SetBinding(TextBlock.TextProperty, "Data");
el.Child = p;
var t = el.Handle;
Debug.Assert(p.Text == "1234");
PS: Found, that everything work better, if you first set DataContext and only then force a Handle to be created (just like my example). But, I think, this is already the case for you, so should not be a problem.
Have you tryed to use the IsDataBound
http://msdn.microsoft.com/en-us/library/system.windows.data.bindingoperations.isdatabound.aspx
Also check this out:
System.Windows.Interop.WindowInteropHelper helper = new System.Windows.Interop.WindowInteropHelper(view).EnsureHandle();
http://msdn.microsoft.com/en-us/library/system.windows.interop.windowinterophelper.ensurehandle.aspx
My other question is why you trying to do a UNIT test on something that has been technically tested already? By the way I am not critising, just want to understand a little better.
Not sure, but maybe something like this will work?
view.GetBindingExpression(Window.TitleProperty).UpdateTarget();
Wondering how to accomplish setting the Style xaml with the code in F#. The code is simple enough:
this.DefaultStyleKey <- typeof<MyControl>
In a C# project the build options allow you to mark the XAML as a resource custom build command of: MSBuild:Compile
I don't see it in the properties panel, so I tried to add it by hand to the project file myself...
Any ideas? The application loads - the custom control has no output (but the code executes).
Thanks
UPDATE:
I checked the manifests and the resource was included as expected between my project and the project I am porting... Looking for a next step.
UPDATE 2:
Well it may be included in the manifest OK - but it is not being "compiled" as the C# version of the project throws an error in the build process when I malform the XML while the F# version allows the malformed XML to be brought into the application.
UPDATE 3:
Loading the XAML is fine now (i guess) however I am having some issues with the properties of the control:
static member ItemsProperty : DependencyProperty =
DependencyProperty.Register(
"Items",
typeof<MyMenuItemCollection>,
typeof<MyMenu>,
null);
member this.Items
with get () : MyMenuItemCollection = this.GetValue(MyMenu.ItemsProperty) :?> MyMenuItemCollection
and set (value: MyMenuItemCollection) = this.SetValue(MyMenu.ItemsProperty, value);
The problem occurs on access:
for menuItem in this.Items do
let contentElement: FrameworkElement = menuItem.Content
where I get a null pointer exception on this.Items; however I have it initialized in the constructor:
do
this.Items <- new CoolMenuItemCollection()
The C# style of compilation of XAML files is not supported by the F# tools for Visual Studio, so there is no way to get the same behavior as in C#. I think you have two options:
Create a C# project with XAML files and reference F# library which implements the core functionality (or reference C# library from F# and load user interface from the C# library in your F# application)
Use XamlReader object (see MSDN) and load the XAML file (embedded in resources in the simple way) programmatically. You won't get any of the C#-compiler generated features (e.g. named properties for all objects with x:Name), but otherwise, it should work in the usual way.
I have an array of sample information that is constantly refreshed in a background thread.
Currently I am constantly assigning this array to a datagrid's ItemsSource property using a DispatcherTimer. That works but it resets any visual locations, for instance if the user places his cursor in the middle of the datagrid the execution timer will undo such position.
Is it possible to use a INotifyPropertyChanged or INotifyCollectionChanged event for this instead to prevent such situations to occur? If so, how does this work with F#?
I suppose I have to execute some function notifying the datagrid every time when there is an update of the array. The updating of the array is not in the STAThread section.
I am running VS2010 with the latest WPF toolkit containing the WPF datagrid.
You can use an ObservableCollection which will implements INotifyCollectionChanged for you. The F# looks something like this:
open System
open System.Collections.ObjectModel
open System.Windows
open System.Windows.Controls
open System.Windows.Threading
[<EntryPoint; STAThread>]
let Main args =
let data = ObservableCollection [0 .. 9]
let list = ListBox(ItemsSource = data)
let win = Window(Content = list, Visibility = Visibility.Visible)
let rnd = Random()
let callback =
EventHandler(fun sender args ->
let idx = rnd.Next(0, 10)
data.[idx] <- rnd.Next(0, 10)
)
let ts = TimeSpan(1000000L)
let dp = DispatcherPriority.Send
let cd = Dispatcher.CurrentDispatcher
let timer = DispatcherTimer(ts, dp, callback, cd) in timer.Start()
let app = Application() in app.Run(win)
Unfortunately Reflector shows that System.Windows.Controls.ItemsControl.OnItemCollectionChanged method removes the selection when it is called, so you may need to work around this default behaviour.
You can also implement INotifyPropertyChanged like so:
open System.ComponentModel
type MyObservable() =
let mutable propval = 0.0
let evt = Event<_,_>()
interface INotifyPropertyChanged with
[<CLIEvent>]
member this.PropertyChanged = evt.Publish
member this.MyProperty
with get() = propval
and set(v) = propval <- v
evt.Trigger(this, PropertyChangedEventArgs("MyProperty"))
Implementing INotifyCollectionChanged would work similarly.
best of luck,
Danny
The ObservableCollection works but unfortunately with issues.
Since the ObservableColection only works when modified in the STAThread, I have to use a dispatcher and basically rewrite, or at least inspect, the complete array as I cannot tell which entries are changed or not.
One thing that is a possibility is to use a F# Mailbox. The background thread could place change messages which could be picked up by a dispatcher in the STAThread. This solution also would remove the need for synchronization.
Does that look like overkill? Anybody done that before? Any alternative solutions?