In my previous question "Enabling dialog OK button with FSharp.ViewModule", I got to the point where a dialog's OK button was enabled only when the validators for the dialog's fields were true, and ViewModule's IsValid property became true. But I ran into a couple more problems after that:
1) Clicking on the OK button didn't close the dialog, even if I set IsDefault="true" in XAML.
2) When the OK button is clicked, sometimes I want to do more checks than provided by the ViewModule validators (eg, checking an email address). Then I want to stop the dialog from closing if this custom validation fails.
But I don't know how to do both when using F# and MVVM. First I tried putting the XAML into a C# project and the view model code in an F# library. And then I used the OK button's Click handler in code behind to close the window. This fixed 1), but not 2).
So this is my XAML:
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Text="{Binding Email, UpdateSourceTrigger=PropertyChanged}" />
<Button Content="OK" IsEnabled="{Binding IsValid}" IsDefault="true" Command="{Binding OkCommand}"
<!--Click="OnOK"--> />
And my view model - with a comment in the validate function to show what I want to do when the OK button is clicked:
let name = self.Factory.Backing( <# self.Name #>, "", notNullOrWhitespace)
let email = self.Factory.Backing( <# self.Email #>, "", notNullOrWhitespace)
let dialogResult = self.Factory.Backing( <# self.DialogResult #>, false )
let isValidEmail (e:string) = e.Length >= 5
member self.Name
with get() = name.Value
and set value = name.Value <- value
member self.Email
with get() = email.Value
and set value = email.Value <- value
member self.DialogResult
with get() = dialogResult.Value
and set value = dialogResult.Value <- value
member self.OkCommand = self.Factory.CommandSync(fun () ->
if not <| isValidEmail(email.Value) then
MessageBox.Show("Invalid Email") |> ignore
else
dialogResult.Value <- true
)
It's worth pointing out that MVVM and code-behind aren't best friends.
The C# event handler you're referring to is located in the Window's code-behind file (i.e. partial class). Although code-behind is considered ok for view related logic, it's frowned upon by MVVM purists. So instead of specifying event handlers in XAML, MVVM prefers the use of Commands.
Option A - Doing it in code-behind, being pragmatic.
Note that FsXaml doesn't provide direct wiring of events (specifying handlers in XAML), but you can wire up the events yourself in code-behind.
After you name a control in XAML, you can get a hold on it in the corresponding source file.
UserDialog.xaml
<Button x:Name="butt" ... >
UserDialog.xaml.fs
namespace Views
open FsXaml
type UserDialogBase = XAML<"UserDialog.xaml">
type UserDialog() as dlg =
inherit UserDialogBase()
do dlg.butt.Click.Add( fun _ -> dlg.DialogResult <- System.Nullable(true) )
Validation is best handled in the ViewModel, e.g. using custom validation for the email adress:
SimpleMVVMDemo
FSharp.ViewModule.Validation.Validators.custom
Option B - You can follow MVVM pattern using a DialogCloser.
First add a new source file at the top of your solution (Solution Explorer)
DialogCloser.fs
namespace Views
open System.Windows
type DialogCloser() =
static let dialogResultProperty =
DependencyProperty.RegisterAttached("DialogResult",
typeof<bool>, typeof<DialogCloser>,
new PropertyMetadata(DialogCloser.DialogResultChanged))
static member SetDialogResult (a:DependencyObject) (value:string) =
a.SetValue(dialogResultProperty, value)
static member DialogResultChanged
(a:DependencyObject) (e:DependencyPropertyChangedEventArgs) =
match a with
| :? Window as window
-> window.DialogResult <- System.Nullable (e.NewValue :?> bool)
| _ -> failwith "Not a Window"
Say our solution is called WpfApp (referenced in XAML header), we can then implement the DialogCloser like this:
UserDialog.xaml
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="clr-namespace:Views;assembly=WpfApp"
xmlns:fsxaml="http://github.com/fsprojects/FsXaml"
views:DialogCloser.DialogResult="{Binding DialogResult}"
>
...
</Window>
Now in the UserDialog's ViewModel you can hook up a Command and close the dialog by setting the dialogResult to true.
member __.OkCommand = __.Factory.CommandSync(fun () ->
if not <| isValidEmail(email.Value) then
System.Windows.MessageBox.Show ("...") |> ignore
else
// do stuff (e.g. saving data)
...
// Terminator
dialogResult.Value <- true
)
You could also skip the if / else clause and validate the email using custom validation.
To wrap it up, you can call the dialog from MainViewModel using this helper function:
UserDialog.xaml.fs
namespace Views
open FsXaml
type UserDialog = XAML<"UserDialog.xaml">
module UserDialogHandling =
/// Show dialog and return result
let getResult() =
let win = UserDialog()
match win.ShowDialog() with
| nullable when nullable.HasValue
-> nullable.Value
| _ -> false
Note that there's no 'code-behind' in this case (no code within the UserDialog type declaration).
Related
Is it possible to make an application in F# that uses WPF with a classic code behind? I know it works perfect with MVVM and no code behind, but I need to implement an interface on a UserControl. Is that possible with F#?
To help a bit, here is the code I want to translate from C# to F#
public class Test : UserControl, IContent {
public void InitializeComponents() {
// Do the initialization magic
}
public Test() {
}
public void OnFragmentNavigation(FragmentNavigationEventArgs e) {
this.DataContext = new { description = "Hallo Welt :)" };
}
public void OnNavigatedFrom(NavigationEventArgs e) {
}
public void OnNavigatedTo(NavigationEventArgs e){
}
public void OnNavigatingFrom(NavigatingCancelEventArgs e) {
}
}
And this is the markup
<UserControl xmlns="http://schemas.microsoft.com/netfx/2007/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Test">
<TextBlock Text="{Binding description}"></TextBlock>
</UserControl>
It really depends on what you mean by "classic code behind". As mentioned by Petr, F# does not have partial classes (and there is also no editor support), so you won't get the same experience (when you are accessing elements or adding events). But you can certainly build a WPF application that uses the same programming model.
One way to get something that is very close to standard code behind is to define a class, associated with each xaml file, that looks something like this:
type SomeComponent() =
let uri = System.Uri("/AppName;component/SomeComponent.xaml", UriKind.Relative)
let ctl = Application.LoadComponent(uri) :?> UserControl
let (?) (this : Control) (prop : string) : 'T =
this.FindName(prop) :?> 'T
let okBtn : Button = ctl?OkButton
do okBtn.Click.Add(fun _ -> (* .. whatever *) )
This loads the XAML content and then uses the dynamic lookup operator to find all the UI elements (which you'd get for free in C#). A nicer F# solution is to use FsXaml, which has a XAML type provider (but sadly, not much documentation).
I found a solution, I just make it short, here is the code:
namespace Testns
open System.Windows.Controls
open FirstFloor.ModernUI.Windows
open Microsoft.FSharp.Core
open System
type TestUserControl() =
inherit UserControl()
interface IContent with
member x.OnFragmentNavigation(e: Navigation.FragmentNavigationEventArgs): unit =
let vm = ViewModel("Hallo Welt :)")
base.DataContext <- vm
()
member x.OnNavigatedFrom(e: Navigation.NavigationEventArgs): unit = ()
member x.OnNavigatedTo(e: Navigation.NavigationEventArgs): unit = ()
member x.OnNavigatingFrom(e: Navigation.NavigatingCancelEventArgs): unit = ()
And the markup
<local:TestUserControl xmlns="http://schemas.microsoft.com/netfx/2007/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Testns;assembly=App">
<TextBlock Text="{Binding description}"></TextBlock>
</local:TestUserControl>
This is not the answer for the actual question, it just works for my use case. So feel free to answer it :)
Probably no. As far as I know F# doesn't support partial classes.
But you can use F# XAML type provider as described here: http://www.mindscapehq.com/blog/index.php/2012/04/29/using-wpf-elements-from-f/
I would like to act on the View from my ViewModelClass by code, not just modifying the properties, to which the XAML binds to.
member x.SelectedUdl
with get() = selectedudl
and set value = selectedudl <- value
//do fancy things like
//this.Quotes12.ItemsSource <- asyncquotes.gethisto (undls |> Seq.head, 2012)
However to do this, I need a reference to the component itself.
Do I have any way to add a reference to "this" from the xaml or other way ?
<UserControl.DataContext>
<ViewModel:QuotesViewModel>
<x:Arguments>
THIS
</x:Arguments>
</ViewModel:QuotesViewModel>
</UserControl.DataContext>
<DockPanel>
type QuotesViewModel(this:Window) =
inherit ViewModelBase()
Edit
I finally added a Global.fs file, in the first position so that it is acessible for all compilation units
module Global
open FSharpx
type MainWindow = XAML<"MainWindow.xaml">
type Singleton private () =
static let instance = Singleton()
let mutable ivalue : MainWindow = null
static member Instance = instance
member this.Value
with get () = ivalue
and set value = ivalue <- value
let window () = Singleton.Instance.Value
Note that it uses the XAML type provider, so you have to move the FMVVM template MainWindow's Frame up a level to benefit from strong type access.
Then in the View, you can add
Global.window().TextBox.Text <- "hello"
With this, we can use both the declarative way, or the code approach, in a strongly typed way.
Initialisation is easily done
let mainWindow = MainWindow()
Global.Singleton.Instance.Value <- mainWindow
// Application Entry point
[<STAThread>]
[<EntryPoint>]
let main(_) =(new Application()).Run(mainWindow.Root)
(recommendations welcome, I am just starting to look at wpf..)
I create some RibbonButtons dynamically and add them to a group according to an xml file. The follwoing function is carried out as often as entries found in the xml file.
private void ExtAppsWalk(ExternalAppsXml p, AppsWalkEventArgs args)
{
RibbonButton rBtn = new RibbonButton();
rBtn.Name = args.Name;
Binding cmdBinding = new Binding("ExtAppCommand");
rBtn.SetBinding(RibbonButton.CommandProperty, cmdBinding);
Binding tagBinding = new Binding("UrlTag");
tagBinding.Mode = BindingMode.OneWayToSource;
rBtn.SetBinding(RibbonButton.TagProperty, tagBinding);
rBtn.Label = args.Haed;
rBtn.Tag = args.Url;
rBtn.Margin = new Thickness(15, 0, 0, 0);
MyHost.ribGrpExtern.Items.Add(rBtn);
}
I tried to use the Tag property to store the Url's to be started when the respective button is clicked. Unfortunately the binding to the Tag property gives me the last inserted Url only.
What would be the best way to figure out which button is hit or to update the Tag property.
The datacontext is by default the context of the Viewmodel. The RibbonGroup to which the Buttons are added is created in the xaml file at designtime. I use that construct:
MyHost.ribGrpExtern.Items.Add(rBtn);
to add the buttons. It maight not really be conform with the mvvm pattern. May be someone else has a better idea to carry that out.
I foud a solution for my problem here and use the RelayCommand class. So I can pass objects (my Url) to the CommandHandler.
RibbonButton rBtn = new RibbonButton();
rBtn.Name = args.Name;
Binding cmdBinding = new Binding("ExtAppCommand");
rBtn.SetBinding(RibbonButton.CommandProperty, cmdBinding);
rBtn.CommandParameter = (object)args.Url;
private void ExtAppFuncExecute(object parameter)
{
if (parameter.ToString().....//myUrl
I am wandering if there is a way of hooking an event defined in XAML to a F# function of member ? Of course, I could do it diagrammatically but it is kind of inconvenient.
I suppose the question is whether you can specify F# member as an event handler using XAML markup:
<Button x:Name="btnClick" Content="Click!" Click="button1_Click" />
As far as I know, the answer is No.
The way this works in C# is that the registration of event handler is done in C# code (partial class) generated by the designer (you can see that in the obj directory in files named e.g. MainForm.g.cs). F# doesn't have any direct support for WPF designer, so it cannot generate this for you. You'll have to write the code to attach event handlers by hand (but that's quite easy).
I have some examples in my London talk about Silverlight. You can implement the ? operator to get nice access to the XAML elements:
type MainPage() as this =
inherit UserControl()
let uri = new System.Uri("/App;component/MainPage.xaml", UriKind.Relative)
do Application.LoadComponent(this, uri)
// Get button using dynamic access and register handler
let btn : Button = this?btnClick
do btnClick.Click.Add(fun _ -> (* ... *))
The ? operator declaration that I used is:
let (?) (this : Control) (prop : string) : 'T = // '
this.FindName(prop) :?> 'T
It is possible to add binding to a command, eg. using the Command="..." property in the button.
So in you XAML you can have:
<Button Command="{Binding MyCommandHandler}">
Then in your ViewModel code, if you have a member called MyCommandHandler, it'll be bound to the above button. So in your F#, something like:
module ViewModel =
type FuncCommand (canExec:(obj -> bool), doExec:(obj -> unit)) =
let theEvent = new DelegateEvent<EventHandler>()
interface ICommand with
[<CLIEvent>]
member x.CanExecuteChanged = theEvent.Publish
member x.CanExecute arg = canExec(arg)
member x.Execute arg = doExec(arg)
type MyViewModel() =
member this.MyCommandHandler =
new FuncCommand(
(fun _ -> ... SOME CODE WHICH RETURNS TRUE OR FALSE ...),
(fun _ -> ... SOME CODE TO HANDLE THE CLICK ...)
)
You can do it using an attached property:
namespace Foo
open System.Windows
open System.Windows.Controls
open System.Windows.Controls.Primitives
open System.Windows.Media
module Register =
// http://stackoverflow.com/a/14706890/1069200
type internal Marker = interface end
let ClickHandlerProperty = DependencyProperty.RegisterAttached(
"ClickHandler",
typeof<RoutedEventHandler>,
typeof<Marker>.DeclaringType,
PropertyMetadata(null))
let SetClickHandler (element: UIElement, value : RoutedEventHandler) =
element.SetValue(ClickHandlerProperty, value)
let GetClickHandler (element: UIElement) : RoutedEventHandler =
element.GetValue(ClickHandlerProperty) :?> _
let private OnClick (sender : obj) args =
let button = sender :?> UIElement
let handler = GetClickHandler button
if not (obj.ReferenceEquals(handler, null)) then
handler.Invoke(sender, args)
let private initialize =
EventManager.RegisterClassHandler(
typeof<FrameworkElement>,
ButtonBase.ClickEvent,
RoutedEventHandler(OnClick))
Then use it in xaml like this:
<Window ...
xmlns:foo="clr-namespace:Foo;assembly=Foo">
<Button Content="Click!" foo:Register.ClickHandler="{x:Static foo:Bar.OnClicked}" />
</Window>
Where bar is:
namespace Foo
open System.Windows
module Bar =
let OnClicked =
let onClick _ _ = MessageBox.Show "clicked" |> ignore
RoutedEventHandler(onClick)
I don't know f# so the above code can probably be cleaned up a lot.
For the click event David's suggestion to bind the command is probably nicest.
This is now supported in the newer versions of FsXaml, though it works slightly differently than it does in C#.
Using FsXaml, you can define your Xaml and specify your event handler. For example, in a window named "MyWindow", you can do:
<Button Content="Click!" Click="button1_Click" />
In your "code behind" file, you would handle this like so:
type MyWindowBase = XAML<"MyWindow.xaml">
type MyWindow () =
inherit MyWindowBase
override this.button1_Click (_,_) = () // Handle event here
update 5: brians solution worked:
namespace Module1
type Page1() as this =
inherit UserControl()
let uriStr = "/FSSilverlightApp;component/Page1.xaml"
let uri = new System.Uri(uriStr, System.UriKind.Relative)
do
Application.LoadComponent(this, uri)
member public this.Uri with get () = uri
type MyApp() as this =
inherit Application()
do Application.LoadComponent(this, new System.Uri("/FSSilverlightApp;component/App.xaml", System.UriKind.Relative))
let nav : Frame = siteTemplate ? contentFrame
let p1 = new Module1.Page1() ;
member this.navigate ea =
nav.Navigate(p1.Uri)
<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Module1.MyApp">
<Application.Resources>
</Application.Resources>
</Application>
<UserControl x:Class="Module1.Page1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid x:Name="LayoutRoot" Background="White">
<TextBlock Text="This is page 1 Lets see if we can ever get here!!!" FontSize="24" />
</Grid>
</UserControl>
update 4:
the template Brian has mentioned is mostly doing the trick. I still have a more complex page that is giving me troubles - yet it is most likely my code. Once my code is complete I will post what I can diagnose but the parts include:
Setting App.XAML correctly (referencing your application object correctly)
in post construction of your application object use Application.Load to load the App.xaml
In your application object create instances of your page xaml
in post construction of your page objects use Application.Load to load the individual page.xaml
each of your page objects should extend UserControl; I suspect this is not truly the case - once I get more more complex page running I will see if removing this restriction will have an effect.
update 3:
I implemented my own controller logic in the Application object, which seems to do part of the trick (and solve my needs for a prototype anyhow).
type Page1() as this =
inherit Page()
do
this.Content <- loadXaml("Page1.xaml")
type MyApp() as this =
inherit Application()
let cc = new ContentControl()
let mainGrid : Grid = loadXaml("MainWindow.xaml")
let siteTemplate : Grid = if mainGrid.Name = "siteTemplate" then mainGrid else mainGrid ? siteTemplate
let nav : Frame = siteTemplate ? contentFrame
let page1 = new Module1.Page1() :> Page ;
let page2 = new Module1.Page2() :> Page ;
let page3 = new Module1.Page3() :> Page ;
do
this.Startup.Add(this.startup)
// to be able to get focus
cc.IsTabStop <- true
cc.IsEnabled <- true
System.Windows.Browser.HtmlPage.Plugin.Focus()
cc.Content <- mainGrid
this.RootVisual <- cc
member this.startup ea =
menu.MenuItemClicked.Add(this.navigate)
resolutionSlider.SizeChanged.Add(this.resizeTemplate)
member this.navigate ea =
if ea.Index = 1 then nav.Content <- page1
elif ea.Index = 2 then nav.Content <- page2
elif ea.Index = 3 then nav.Content <- page3
It works... I don't know the implication on memory / performance. I wonder if the navigation fw handles the construction / destruction of page objects more efficiently than what I did. I think the navigation FW works nicely with the browsers back and forward buttons - which my solution doesn't.
update 2: it looks as though teh C# applciation implments
public void InitializeComponent()
which loads and the XAML. Though I am no IL expert; I will make the similar changes on the F# side... I wonder if it is the partial class concept. One theory I am working on is:
page.xaml.cs is definitely a partial class - you can read it in the source.
page.xaml has an attribute that refers back to the c# class. I wonder if the special build commands treat this as a partial class - by parsing it and creating 1) any member component references 2) intialComponent() method which registers the page wherever it needs to be registered?
Update 1: After a nights sleep the problem can be stated more accurately as I have a 100% f# / silverlight implementation and am looking to use the built in Navigation components. C# creates page.xaml and page.xaml.cs um - ok; but what is the relationship at a fundamental level? How would I go about doing this in f#?
The applcuation is loaded in the default module, and I pull the XAML in and reference it from the application object. Do I need to create instances / references to the pages from within the application object? Or set up some other page management object with the proper name value pairs?
When all the Help of VS is stripped away - what are we left with?
original post (for those who may be reading replies)
I have a 100% silverlight 3.0 / f# 2.0 application I am wrapping my brain around. I have the base application loading correctly - and now I want to add the naigation controls to it.
My page is stored as an embedded resource - but the Frame.Navigate takes a URI. I know what I have is wrong but here it is:
let nav : Frame = mainGrid ? mainFrame
let url = "/page1.xaml"
let uri = new System.Uri(url, System.UriKind.Relative) ;
nav.Navigate uri
Any thoughts?
Have you tried making the Xaml a file in the project with a BuildAction of Content rather than an EmbeddedResource? Honestly, I've no clue if that works, but it might get packaged into the .xap that way, and then the relative uri might work. How would it work in a C# project? Try that.
EDIT
Aha, Dmitry's template appears to have this figured out. He has Xaml files with BuildAction of Resource, and then code like
type MainPage() as this =
inherit UserControl()
do
Application.LoadComponent(this,
new System.Uri("/SilverlightApplication3;component/Page.xaml",
System.UriKind.Relative))
let layoutRoot : Grid = downcast this.FindName("LayoutRoot")
do
()
to load it.