Coder Social home page Coder Social logo

wpffs's Introduction

Update This code base hasn't been maintained for years since there is better library at https://github.com/fsprojects/FsXaml.

Introduction

This library is built as an alternative to use XAML in F#.

Nuget repository

Library is available at Nuget

Code Behind in F#

While FsXaml's Type Provider is useful and simple, it does not give ease to work with XAML compared with C#. I propose an alternative solution by following example. Suppose we have a "Hello, World!" XAML as a project Resource.

Sample.xaml

<Window xmlns="http://schemas.microsoft.com/netfx/2009/xaml/presentation">
  <StackPanel>
    <Label>Hello, world!</Label>
    <Button>Click me!</Button>
  </StackPanel>
</Window>

To declare type for this window with FsXaml Type Provider.

type MainWindow = FsXaml.XAML<"Sample.xaml">

But using this library, the declaration becomes following:

open System.Windows
open RZ.Wpf.CodeBehind

type MainWindow() as me =
  inherit Window()
  do me.InitializeCodeBehind("Sample.xaml")

It is longer, but it gives you flexibility to extend the control like in C# (e.g. make a User Control with custom properties).

Handle Routed Events (1.0.1)

Version 1.0.1 has introduced a way to handle routed events. This works between View Model, which is assigned to DataContext, and code behind (as the view logic). This design uses some idea from MVVM of FsXaml and FsXaml.ViewModel libraries.

Suppose that our sample XAML utilizes Command pattern.

Sample.xaml

<Window xmlns="http://schemas.microsoft.com/netfx/2009/xaml/presentation"
        xmlns:model="clr-namespace:Sample;assembly=Sample">
  <Window.DataContext>
    <model:MainWindowModel />
  </Window.DataContext>
  <StackPanel>
    <Label>Hello, world!</Label>
    <Button Command="Open" CommandParameter="http://google.com/">Click me!</Button>
  </StackPanel>
</Window>

Codebehind.fs

Here we create a view model and use it in the XAML. The model is a bridge between View and Model (or Controller).

namespace Sample
open System.Windows
open System.Windows.Input
open RZ.Wpf.CodeBehind

type MainWindowEvent =
| Help of string

type MainWindowModel() =
  let handler = function
    | Help url -> System.Diagnostics.Process.Start url |> ignore

  let mapper =
    [ ApplicationCommands.Open |> CommandMap.to' (fun p -> Help (p :?> string)) ]
    |> CommandControlCenter handler
    
  interface ICommandHandler with
    member __.ControlCenter = mapper

type MainWindow() as me =
  inherit Window()
  do me.InitializeCodeBehind("Sample.xaml")
  do me.InstallCommandForwarder()   // install command forwarder to `DataContext`'s ICommandHandler.

With this sample, when you click the button it will raise command Open which will be forward to ICommandHandler via DataContext. The ICommandHandler will return command handler which, in this case, is created by pre-defined function in the library. Finally, the handler is called with the raised command accordingly.

XamlLoader changes (1.1.0)

XamlLoader module is re-written and now has simple 4 functions to create/load XAML object.

Function Description
createFromFile: string -> obj option Load XAML from file and instantiate.
createFromResource: string -> Assembly -> obj option Load XAML from the assembly's resource
createFromXaml: string -> obj Create directly from XAML string.
createFromXamlObj: obj -> string -> obj Create directly from XAML string with root object.

Note that createFromXaml and createFromXamlObj functions do not return option. These functions throw an exception if XAML text is malformed or incompatible with its code behind class. This behavior is by design for most of usage, IMO, is in an application itself and bad XAML should be seriously fixed.

Event to command markup extension (1.1.1)

This extension allows converting an event into a command directly without using Blend SDK.

Sample.xaml

<Window xmlns="http://schemas.microsoft.com/netfx/2009/xaml/presentation"
        xmlns:rzmk="clr-namespace:RZ.Wpf.Markups;assembly=RZ.Wpf"
        xmlns:model="clr-namespace:Sample;assembly=Sample">
  <Window.DataContext>
    <model:MainWindowModel />
  </Window.DataContext>
  <StackPanel>
    <Label>Hello, world!</Label>
    <Button Command="{rzmk:BindCommand Open, CommandParameter=http://google.com/}">Click me to fire Open command.</Button>
  </StackPanel>
</Window>

When the button is clicked, Open command will be raised with command parameter "http://google.com/.

Event argument can be transformed and passed as command parameter as well by using EventArgumentConverter parameter. For example:

<Button Click="{rzmk:BindCommand Open, CommandParameter=Hiya, EventArgumentConverter=ToUpper}">Open command with 'HIYA'</Button>

ToUpper is a method in Data Context (this library is designed with Data Context cooperation in mind).

// snippet
type BindCommandSampleModel() as me =
  inherit ViewModelBase()

  member __.ToUpper(param: string, e: RoutedEventArgs) = param.ToUpper()

First parameter of a converter must always be string. The second parameter can be anything that is compatible with the event type. For example, if binding with MouseEnter event, this second parameter can be MouseEventArgs type. The return type of the converter can be anything but DependencyProperty.UnsetValue, it will directly be passed as a command parameter.

If the return is DependencyProperty.UnsetValue, the event will not be handled and command will not be raised.

Binding a custom command

BindCommand extension has CommandType parameter that can be either Standard(default) or ContextCommand. If it is set to ContextCommand, the specified command will be dynamically resolve at runtime with data context of the sender.

See more examples in BindCommandSample.xaml.

Other notes

Examples in this project come from the reference of book "WPF 5 Unleashed"

wpffs's People

Contributors

ruxo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

wpffs's Issues

How do I define a bindable property?

I have this

open System.Windows.Controls
open System.Windows
open RZ.Wpf.CodeBehind

type DataPagerTextBox () as me = 
    inherit UserControl()
    do me.InitializeCodeBehind("DataPagerTextBox.xaml")
    static member PageIndexProperty:DependencyProperty = DependencyProperty.RegisterAttached("PageIndex",typeof<int>, typeof<DataPagerTextBox>)
    member x.PageIndex with get() = me.GetValue(DataPagerTextBox.PageIndexProperty) :?> int and set (v:int) = me.SetValue(DataPagerTextBox.PageIndexProperty, v) 

with xaml

<UserControl
        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" 
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:behavior="clr-namespace:Pm.UI.Behaviors;assembly=Pm.UI"
        mc:Ignorable="d" 
        xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
        xmlns:viewModels="clr-namespace:Pm.UI.ViewModels"
        Name="DataPagerTextBox"
>
    <Grid>
        <Grid.Resources>
            <ResourceDictionary>
                <ResourceDictionary.MergedDictionaries>
                    <ResourceDictionary Source="ApplicationResources.xaml"/>
                </ResourceDictionary.MergedDictionaries>
            </ResourceDictionary>
        </Grid.Resources>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
        </Grid.RowDefinitions>
        <Label>Hello DataPagerTextBox</Label> 
    </Grid>
</UserControl>

and attemping to consume it via

<local:DataPagerTextBox Height="22" IsEnabled="False" Margin="2,0,2,0" PageIndex="{TemplateBinding local:DataPagerPresenter.PageIndex}" VerticalAlignment="Center" VerticalContentAlignment="Center" Width="48" xml:space="preserve">
                                            >

image

I have built the project, so it's not that simple problem.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.