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
Related
So far I have managed to create a ribbon with several buttons by following this tutorial.
Now I want to make the buttons actually do stuff! I found this example which shows how to execute already existing method. However if I do: Command="myCustomClass.myCustomMethod" and use
Class myCustomClass
Public Sub myCustomMethod()
'do stuff
End Sub
End Class
I get the error myCustomClass is not supported in a Windows Presentation Foundation (WPF) project.
I also noticed this this post from the same post. After converting the code to VB.NET:
Public Class MyCommand
Implements ICommand
Public Sub Execute(parameter As Object) Implements ICommand.Execute
Dim hello As String = TryCast(parameter, String)
MsgBox(hello)
End Sub
Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
Return True
End Function
Public Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
End Class
and adding the references to the <Window x:Class="MainWindow" ... element:
<Window.Resources>
<local:MyCommand x:Key="mycmd"/>
</Window.Resources>
I get the following error: The name 'MyCommand' does not exist in the namespace "clr-namespace:RibbonSample".
I'm at a complete loss as to what I need to do to implement these buttons. Would it be possible to get any full ribbon samples using the same framework, with which I can learn from? I assume the solution to this should be really simple.
Rebuild your project.
The WPF designer loads classes from your project's compiled assembly (so that it can actually execute your code).
If you add a new class, it will give you errors until you rebuild your project so the assembly includes that class.
If I use Application.LoadComponent() to load a UserControl, Page or Window, my application freezes when I try to close it.
The app apparently closes, but the process keeps running. Easy to notice when debugging.
I've tested it under Windows 7 64bit and Vista 32bit. In both cases I have used VS2008 and .NET 3.5.
A repro can be built by creating a wpf application as follows:
public partial class Window1 : Window {
public Window1() {
InitializeComponent();
}
public void LoadCopy() {
var uri = new Uri("/WpfApplication1;component/window1.xaml", UriKind.Relative);
var copy = (Window)Application.LoadComponent(uri);
MessageBox.Show(copy.Title);
}
private void Button_Click(object sender, EventArgs e) {
LoadCopy();
}
}
Does anyone know what might be happening? And how to solve it?
Try assigning the owner to the created assembly i.e.
copy.Owner = this;
I was able to close your example after doing this.
I think it is because you are calling LoadComponent() on what is also your Main Window ( http://msdn.microsoft.com/en-us/library/system.windows.application.mainwindow.aspx ), i.e. the startup uri, in your case Window1. The program is probably entering some loop when you close it because closing a Main Window by default shuts down the Application and your two instances of Window1 are waiting on each other (A.K.A. a deadlock)! Albeit seemingly only after making the Application invisible (so it seems to have closed).
If you still must use use LoadComponent() on Window1 I think you would need to not make it your startup uri by changing the StartupUri of your Application:
<Application
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Window1.xaml"> <!-- change this -->
</Application>
Or change Application.ShutdownMode ( http://msdn.microsoft.com/en-us/library/system.windows.application.shutdownmode.aspx ) to OnLastWindowClose:
<Application
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Window1.xaml"
ShutdownMode="OnLastWindowClose">
</Application>
I have build you application on Windows 7 32bit under .Net 4.0 and 3.5.
I works fine for me. I think you problem is configuration specific.
Which configuration do you have? Do you reference any assemblies except default WPF project references?
I got my hands om MEF for a week now and I am trying to build up a WPF application that loads imported controls from MEF.
I created a WPF application project and removed the default window and application start up URI. Then I handled the application startup event to compose the application:
public partial class App : Application, IPartImportsSatisfiedNotification
{
{...}
private void App_Startup(object sender, StartupEventArgs e)
{
this.Compose();
}
public void Compose()
{
try
{
globalCatalog.Catalogs.Add(new DirectoryCatalog(extensionsDirectoryPath));
CompositionContainer container = new CompositionContainer(globalCatalog);
container.ComposeParts(this);
}
catch (Exception ex)
{
// Do something
}
}
{...}
}
Actually, when debugging and watching objects after imports are satisfied, everything has hierarchically composed fine like I wanted. But when I try to show up the MainWindow of the application an exception is thrown on MainWindow.Show() call:
"Specified element is already the logical child of another element. Disconnect it first."
Though my code in OnImportsSatisfied method seems fine as it is working when not using MEF mecanism:
public void OnImportsSatisfied()
{
Window mainWindow = new Window();
mainWindow.Content = this.importedControl;
this.MainWindow = mainWindow;
this.MainWindow.Show();
}
I insist on the fact that this works perfectly when not importing controls with MEF. What is surprising is that this code does not work too:
Window mainWindow = new Window();
//mainWindow.Content = this.importedControl;
this.MainWindow = mainWindow;
this.MainWindow.Show();
So I suspect that ComposeParts is doing a bit more than what it says as it is the only member acting on my actual application instance.
Hope someone can help me (Glenn?).
Thanks.
Edit:
I discovered that when I remove the IPartImportsSatisfiedNotification interface from my parts, no exception is thrown and the window shows up. But of course the window is empty as I need this OnImportsSatisfied method to set the DataContext of the window to its associated imported view model.
The sample applications of the WPF Application Framework (WAF) show how to use MEF within a WPF application.
I finally discovered that I was importing my WPF user controls by using the default ImportAttribute constructor, which in fact will make a shared instance of the class if the creation policy is not specified during export. And as many of my controls were implementing the same interface and I was binding them in my views, I was actually trying to add this shared user control instance to different visual elements, which is not permited by WPF (and so the exception).
I marked my imports using the RequiredCreationPolicy set to NonShared and everything got back in order! That was all about learning MEF...
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
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.