演练:创建可移植 F# 库

通过遵循本演练,您可以在与 Silverlight 应用程序、传统桌面应用程序,或者通过使用 .NET API 创建的 Windows 应用商店 的应用程序一起使用的 F# 中创建程序集。通过这种方式,可以以另一 .NET 语言来编写应用程序的 UI 部分,例如 C# 或 Visual Basic,以及以 F# 语言编写的算法部分。您还可能支持面向不同平台的不同用户界面。

您不能直接从 F# 中使用 Windows 应用商店 UI,因此我们建议您在另一个 .NET 语言中写入 Windows 应用商店 应用程序的 UI 并在可移植库中写入 F# 代码。可以在 F# 中直接编写 Silverlight 和 Windows Presentation Foundation (WPF) UI,但是,您可能希望利用在 Visual Studio 中写入 C# 或 Visual Basic 代码时可用的其他设计工具。

系统必备

若要创建 Windows 应用商店 应用程序,则开发计算机上必须具有 Windows 8。

若要创建 Silverlight 项目,则开发计算机上必须具有 Silverlight 5。

电子表格应用程序

在本演练中,将开发向用户显示网格并在其单元格内支持数值输入和公式的简单电子表格。F# 层处理并验证所有输入,特别是分析公式文本并计算公式的结果。首先,将创建 F# 算法代码,包括设计单元格引用、数字和数学运算符的分析表达式的代码。此应用程序还包括代码跟踪哪些单元格必须更新,在用户更新另一个单元格的内容时。接下来,您将创建用户界面。

下面的图形演示您将在本演练中创建的应用程序。

电子表格应用程序的用户界面

最终应用程序的 F# 可移植演练屏幕快照

本演练有以下各节。

  • How To: Create an F# Portable Library

  • How To: Create a Silverlight App that Uses an F# Portable Library

  • How To: Create a ... Style App That Uses an F# Portable Library

  • How to: Create a Desktop App That References a Portable Library That Uses F#

如何:创建 F# 可移植库

  1. 在菜单栏上,选择**“文件”“新建项目”。在“新项目”对话框中,展开“visual F#”,选择“F# 可移植库”**项目类型,然后命名库 Spreadsheet。注意:该项目引用的是特殊版本的 FSharp.Core。

  2. 在**“解决方案资源管理器”中,展开“引用”节点,然后选择 FSharp.Core 节点。在“属性”窗口中,“FullPath”**属性值应包含 .NETPortable,指示您使用的是可移植版本的核心 F# 库。还可以查看默认情况下可以访问的各种 .NET 库。这些库所有工作是定义.NET .NET framework 的一个常见子集作为 .NET 可移植。您可以移除不需要的引用,但是,如果您添加引用,则引用程序集必须在面向的所有平台上可用。程序集的说明文件通常指示它是可用的平台。

  3. 打开项目的快捷菜单,然后选择**“属性”。在“应用程序”选项卡上,将目标框架设置更改为“.NET 可移植子集”**。对于 Visual Studio 2012,此子集的目标是 Windows 应用商店 应用程序的 .NET、.NET framework 4.5 和 Silverlight 5。这些设置很重要,因为,作为可移植库,应用程序必须运行在可用的不同的平台上运行时。Windows 应用商店 应用程序的运行时,和 Silverlight 5 包含完整 .NET Framework 的子集。

  4. 重命名主代码文件 Spreadsheet.fs,然后将下面的代码粘贴到编辑器窗口。此代码定义基本电子表格的功能。

    namespace Portable.Samples.Spreadsheet
    
    open System
    open System.Collections.Generic
    
    [<AutoOpen>]
    module Extensions = 
        type HashSet<'T> with
            member this.AddUnit(v) = ignore( this.Add(v) )
    
    type internal Reference = string
    
    /// Result of formula evaluation
    [<RequireQualifiedAccess>]
    type internal EvalResult = 
        | Success of obj
        | Error of string
    
    /// Function that resolves reference to value.
    /// If formula that computes value fails, this function should also return failure.
    type internal ResolutionContext = Reference -> EvalResult
    
    /// Parsed expression
    [<RequireQualifiedAccess>]
    type internal Expression = 
        | Val of obj
        | Ref of Reference
        | Op of (ResolutionContext -> list<Expression> -> EvalResult) * list<Expression>
        with 
        member this.GetReferences() = 
            match this with
            | Expression.Ref r -> Set.singleton r
            | Expression.Val _ -> Set.empty
            | Expression.Op (_, args) -> (Set.empty, args) ||> List.fold (fun acc arg -> acc + arg.GetReferences())
    
    [<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
    module internal Operations = 
        
        let eval (ctx : ResolutionContext) = 
            function
            | Expression.Val v -> EvalResult.Success v
            | Expression.Ref r -> ctx r
            | Expression.Op (f, args) -> try f ctx args with e -> EvalResult.Error e.Message
        
        type private Eval = Do 
            with 
            member this.Return(v) = EvalResult.Success v
            member this.ReturnFrom(v) = v
            member this.Bind(r, f) = 
                match r with
                | EvalResult.Success v -> f v
                | EvalResult.Error _-> r
    
        let private mkBinaryOperation<'A, 'R> (op : 'A -> 'A -> 'R) ctx =
            function
            | [a; b] -> 
                Eval.Do {
                    let! ra = eval ctx a
                    let! rb = eval ctx b
                    match ra, rb with
                    | (:? 'A as ra), (:? 'A as rb) -> return op ra rb
                    | _ -> return! EvalResult.Error "Unexpected type of argument"
                }
            | _ -> EvalResult.Error "invalid number of arguments"
    
        let add = mkBinaryOperation<float, float> (+)
        let sub = mkBinaryOperation<float, float> (-)
        let mul = mkBinaryOperation<float, float> (*)
        let div = mkBinaryOperation<float, float> (/)
    
        let ge = mkBinaryOperation<float, bool> (>=)
        let gt = mkBinaryOperation<float, bool> (>)
    
        let le = mkBinaryOperation<float, bool> (<=)
        let lt = mkBinaryOperation<float, bool> (<)
    
        let eq = mkBinaryOperation<IComparable, bool> (=)
        let neq = mkBinaryOperation<IComparable, bool> (<>)
    
        let mmax = mkBinaryOperation<float, float> max
        let mmin = mkBinaryOperation<float, float> min
    
        let iif ctx = 
            function
            | [cond; ifTrue; ifFalse] -> 
                Eval.Do {
                    let! condValue = eval ctx cond
                    match condValue with
                    | :? bool as condValue-> 
                        let e = if condValue then ifTrue else ifFalse
                        return! eval ctx e
                    | _ -> return! EvalResult.Error "Condition should be evaluated to bool"
                }
            | _ -> EvalResult.Error "invalid number of arguments"
        
        let get (name : string) = 
            match name.ToUpper() with
            | "MAX" -> mmax
            | "MIN" -> mmin
            | "IF" -> iif
            | x -> failwithf "unknown operation %s" x
    
    module internal Parser =
        let private some v (rest : string) = Some(v, rest)
        let private capture pattern text =
            let m = System.Text.RegularExpressions.Regex.Match(text, "^(" + pattern + ")(.*)")
            if m.Success then
                some m.Groups.[1].Value m.Groups.[2].Value
            else None
        let private matchValue pattern = (capture @"\s*") >> (Option.bind (snd >> capture pattern))
    
        let private matchSymbol pattern = (matchValue pattern) >> (Option.bind (snd >> Some))
        let private (|NUMBER|_|) = matchValue @"-?\d+\.?\d*"
        let private (|IDENTIFIER|_|) = matchValue @"[A-Za-z]\w*"
        let private (|LPAREN|_|) = matchSymbol @"\("
        let private (|RPAREN|_|) = matchSymbol @"\)"
        let private (|PLUS|_|) = matchSymbol @"\+"
        let private (|MINUS|_|) = matchSymbol @"-"
        let private (|GT|_|) = matchSymbol @">"
        let private (|GE|_|) = matchSymbol @">="
        let private (|LT|_|) = matchSymbol @"<"
        let private (|LE|_|) = matchSymbol @"<="
        let private (|EQ|_|) = matchSymbol @"="
        let private (|NEQ|_|) = matchSymbol @"<>"
        let private (|MUL|_|) = matchSymbol @"\*"
        let private (|DIV|_|) = matchSymbol @"/"
        let private (|COMMA|_|) = matchSymbol @","
        let private operation op args rest = some (Expression.Op(op, args)) rest
        let rec private (|Factor|_|) = function
            | IDENTIFIER(id, r) ->
                match r with
                | LPAREN (ArgList (args, RPAREN r)) -> operation (Operations.get id) args r
                | _ -> some(Expression.Ref id) r
            | NUMBER (v, r) -> some (Expression.Val (float v)) r
            | LPAREN(Logical (e, RPAREN r)) -> some e r
            | _ -> None
    
        and private (|ArgList|_|) = function
            | Logical(e, r) ->
                match r with
                | COMMA (ArgList(t, r1)) -> some (e::t) r1
                | _ -> some [e] r
            | rest -> some [] rest
    
        and private (|Term|_|) = function
            | Factor(e, r) ->
                match r with
                | MUL (Term(r, rest)) -> operation Operations.mul [e; r] rest
                | DIV (Term(r, rest)) -> operation Operations.div [e; r] rest
                | _ -> some e r
            | _ -> None
    
        and private (|Expr|_|) = function
            | Term(e, r) ->
                match r with
                | PLUS (Expr(r, rest)) -> operation Operations.add [e; r] rest
                | MINUS (Expr(r, rest)) -> operation Operations.sub [e; r] rest
                | _ -> some e r
            | _ -> None
    
        and private (|Logical|_|) = function
            | Expr(l, r) ->
                match r with
                | GE (Logical(r, rest)) -> operation Operations.ge [l; r] rest
                | GT (Logical(r, rest)) -> operation Operations.gt [l; r] rest
                | LE (Logical(r, rest)) -> operation Operations.le [l; r] rest
                | LT (Logical(r, rest)) -> operation Operations.lt [l; r] rest
                | EQ (Logical(r, rest)) -> operation Operations.eq [l; r] rest
                | NEQ (Logical(r, rest)) -> operation Operations.neq [l; r] rest
                | _ -> some l r
            | _ -> None
    
        and private (|Formula|_|) (s : string) =
            if s.StartsWith("=") then
                match s.Substring(1) with
                | Logical(l, t) when System.String.IsNullOrEmpty(t) -> Some l
                | _ -> None
            else None
    
        let parse text = 
            match text with
            | Formula f -> Some f
            | _ -> None
    
    type internal CellReference = string
    
    module internal Dependencies = 
    
        type Graph() = 
            let map = new Dictionary<CellReference, HashSet<CellReference>>()
    
            let ensureGraphHasNoCycles(cellRef) =
                let visited = HashSet()
                let rec go cycles s =
                    if Set.contains s cycles then failwith ("Cycle detected:" + (String.concat "," cycles))
                    if visited.Contains s then cycles
                    else
                    visited.AddUnit s
                    if map.ContainsKey s then
                        let children = map.[s]
                        ((Set.add s cycles), children)
                            ||> Seq.fold go
                            |> (fun cycle -> Set.remove s cycles)
                    else
                        cycles
    
                ignore (go Set.empty cellRef)
    
            member this.Insert(cell, parentCells) = 
                for p in parentCells do
                    let parentSet = 
                        match map.TryGetValue p with
                        | true, set -> set
                        | false, _ ->
                            let set = HashSet()
                            map.Add(p, set)
                            set
                    parentSet.AddUnit cell
                try 
                    ensureGraphHasNoCycles cell
                with
                    _ -> 
                    this.Delete(cell, parentCells)
                    reraise()
                                 
            member this.GetDependents(cell) = 
                let visited = HashSet()
                let order = Queue()
                let rec visit curr = 
                    if not (visited.Contains curr) then 
                        visited.AddUnit curr
                        order.Enqueue(curr)
                        match map.TryGetValue curr with
                        | true, children -> 
                            for ch in children do
                                visit ch
                        | _ -> ()
    
                        
                visit cell
                order :> seq<_>
    
            member this.Delete(cell, parentCells) = 
                for p in parentCells do
                    map.[p].Remove(cell)
                    |> ignore
    
    type Cell = 
        {
            Reference : CellReference
            Value : string
            RawValue : string
            HasError : bool
        }
    
    type RowReferences = 
        {
            Name : string
            Cells : string[]
        }
    
    type Spreadsheet(height : int, width : int) = 
        
        do 
            if height <=0 then failwith "Height should be greater than zero"
            if width <=0 || width > 26 then failwith "Width should be greater than zero and lesser than 26"
    
        let rowNames = [| for i = 0 to height - 1 do yield string (i + 1)|]
        let colNames = [| for i = 0 to (width - 1) do yield string (char (int 'A' + i)) |]
    
        let isValidReference (s : string) = 
            if s.Length < 2 then false
            else
            let c = s.[0..0]
            let r = s.[1..]
            (Array.exists ((=)c) colNames) && (Array.exists ((=)r) rowNames)
    
        let dependencies = Dependencies.Graph()
        let formulas = Dictionary<_, Expression>()
    
        let values = Dictionary()
        let rawValues = Dictionary()
    
        let setError cell text = 
            values.[cell] <- EvalResult.Error text
    
        let getValue reference = 
            match values.TryGetValue reference with
            | true, v -> v
            | _ -> EvalResult.Success 0.0
        
        let deleteValue reference = 
            values.Remove(reference)
            |> ignore
    
        let deleteFormula cell = 
            match formulas.TryGetValue cell with
            | true, expr ->
                dependencies.Delete(cell, expr.GetReferences())
                formulas.Remove(cell) 
                |> ignore
            | _ -> ()
    
        let evaluate cell = 
            let deps = dependencies.GetDependents cell
            for d in deps do
                match formulas.TryGetValue d with
                | true, e -> 
                    let r = Operations.eval getValue e
                    values.[d] <- r
                | _ -> ()
            deps
    
        let setFormula cell text = 
            let setError msg = 
                setError cell msg
                [cell] :> seq<_>
            
            try 
                match Parser.parse text with
                | Some expr ->
                    let references = expr.GetReferences()
                    let invalidReferences = [for r in references do if not (isValidReference r) then yield r]
                    if not (List.isEmpty invalidReferences) then
                        let msg = sprintf "Formula contains invalid references:%s" (String.concat ", " invalidReferences)
                        setError msg
                    else
                    try
                        dependencies.Insert(cell, references)
                        formulas.Add(cell, expr)
                        |> ignore
                        evaluate cell
                    with
                        e -> setError e.Message
                | _ -> setError "Invalid formula text"
            with e -> setError e.Message
    
        member this.Headers = colNames
        member this.Rows = rowNames
        member this.GetRowReferences() = 
            seq { for r in rowNames do
                  let cells = [| for c in colNames do yield c + r |]
                  yield { Name = r; Cells = cells } }
    
        member this.SetValue(cellRef : Reference, value : string) : Cell[] = 
            rawValues.Remove(cellRef)
            |> ignore
    
            if not (String.IsNullOrEmpty value) then
                rawValues.[cellRef] <- value
    
            deleteFormula cellRef
            
            let affectedCells = 
                if (value <> null && value.StartsWith "=") then
                    setFormula cellRef value
                elif String.IsNullOrEmpty value then
                    deleteValue cellRef
                    evaluate cellRef
                else
                    match Double.TryParse value with
                    | true, value -> 
                        values.[cellRef] <- EvalResult.Success value
                        evaluate cellRef
                    | _ -> 
                        values.[cellRef] <- EvalResult.Error "Number expected"
                        [cellRef] :> _
            [| for r in affectedCells do 
                let rawValue = 
                    match rawValues.TryGetValue r with
                    | true, v -> v
                    | false, _ -> ""
    
                let valueStr, hasErr = 
                    match values.TryGetValue r with
                    | true, (EvalResult.Success v) -> (string v), false
                    | true, (EvalResult.Error msg) -> msg, true
                    | false, _ -> "", false
                let c = {Reference = r; Value = valueStr; RawValue = rawValue; HasError = hasErr}
                yield c |]
    

如何:创建使用 F# 可移植库的 Silverlight 应用程序

  1. 在菜单栏上选择**“文件”“添加”,然后“新建项目”。在“添加新项目”对话框中,依次展开“Visual C#”Silverlight,然后选择“Silverlight Application”。此时将出现“新建 Silverlight 应用程序”**对话框。

  2. 确保选择**“在新网站中承载 Silverlight 应用程序”的复选框,然后在下拉菜单中,确保选择“ASP.NET Web 应用程序项目”,然后选择“确定”**按钮。创建两个项目:一个项目具有 Silverlight 控件,而另一个项目则是承载控件的 ASP.NET web 应用程序。

  3. 向该“电子表格”项目添加引用。打开 Silverlight 项目的引用节点的快捷菜单,然后选择**“添加引用”。引用管理器出现。展开“解决方案”节点,并选择“电子表格”项目,然后选择“确定”**按钮。

  4. 在此步骤中,创建一个视图模型,该模型描述 UI 必须执行的任何任务,而不描述其显示方式。打开项目节点的快捷菜单,选择**“添加”**,然后选择 “新项目”。添加一个代码文件,将其命名为 ViewModel.cs,然后将以下代码粘贴到其中:

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Input;
    using Portable.Samples.Spreadsheet;
    
    namespace SilverlightFrontEnd
    {
        public class SpreadsheetViewModel
        {
            private Spreadsheet spreadsheet;
            private Dictionary<string, CellViewModel> cells = new Dictionary<string, CellViewModel>();
    
            public List<RowViewModel> Rows { get; private set; }
            public List<string> Headers { get; private set; }
    
    
            public string SourceCode
            {
                get
                {
                    return @"
    type Spreadsheet(height : int, width : int) = 
    
        do 
            if height <= 0 then failwith ""Height should be greater than zero""
            if width <= 0 || width > 26 then failwith ""Width should be greater than zero and lesser than 26""
    
        let rowNames = [| for i = 0 to height - 1 do yield string (i + 1)|]
        let colNames = [| for i = 0 to (width - 1) do yield string (char (int 'A' + i)) |]
    
        let isValidReference (s : string) = 
            if s.Length < 2 then false
            else
            let c = s.[0..0]
            let r = s.[1..]
            (Array.exists ((=)c) colNames) && (Array.exists ((=)r) rowNames)
    
        let dependencies = Dependencies.Graph()
        let formulas = Dictionary<_, Expression>()
    
        let values = Dictionary()
        let rawValues = Dictionary()
    
        let setError cell text = 
            values.[cell] <- EvalResult.E text
    
        let getValue reference = 
            match values.TryGetValue reference with
            | true, v -> v
            | _ -> EvalResult.S 0.0
    
        let deleteValue reference = 
            values.Remove(reference)
            |> ignore
    
        let deleteFormula cell = 
            match formulas.TryGetValue cell with
            | true, expr ->
                dependencies.Delete(cell, expr.GetReferences())
                formulas.Remove(cell) 
                |> ignore
            | _ -> ()
    ";
                }
            }
    
            public SpreadsheetViewModel(Spreadsheet spreadsheet)
            {
                this.spreadsheet = spreadsheet;
                Rows = new List<RowViewModel>();
                foreach (var rowRef in spreadsheet.GetRowReferences())
                {
                    var rowvm = new RowViewModel { Index = rowRef.Name, Cells = new List<CellViewModel>() };
    
                    foreach (var reference in rowRef.Cells)
                    {
                        var cell = new CellViewModel(this, reference);
                        cells.Add(reference, cell);
                        rowvm.Cells.Add(cell);
                    }
                    Rows.Add(rowvm);
    
                }
                Headers = new[] { "  " }.Concat(spreadsheet.Headers).ToList();
            }
    
            public void SetCellValue(string reference, string newText)
            {
                var affectedCells = spreadsheet.SetValue(reference, newText);
                foreach (var cell in affectedCells)
                {
                    var cellVm = cells[cell.Reference];
                    cellVm.RawValue = cell.RawValue;
    
                    if (cell.HasError)
                    {
                        cellVm.Value = "#ERROR";
                        cellVm.Tooltip = cell.Value; // will contain error
                    }
                    else
                    {
                        cellVm.Value = cell.Value;
                        cellVm.Tooltip = cell.RawValue;
                    }
                }
            }
        }
    
        public class RowViewModel
        {
            public string Index { get; set; }
            public List<CellViewModel> Cells { get; set; }
        }
    
        public class CellViewModel : INotifyPropertyChanged
        {
            private SpreadsheetViewModel spreadsheet;
    
            private string rawValue;
            private string value;
            private string reference;
            private string tooltip;
    
            public CellViewModel(SpreadsheetViewModel spreadsheet, string reference)
            {
                this.spreadsheet = spreadsheet;
                this.reference = reference;
            }
    
            public string RawValue
            {
                get
                {
                    return rawValue;
                }
                set
                {
                    var changed = rawValue != value;
                    rawValue = value;
                    if (changed) RaisePropertyChanged("RawValue");
                }
            }
            public string Value
            {
                get
                {
                    return value;
                }
                set
                {
                    var changed = this.value != value;
                    this.value = value;
                    if (changed) RaisePropertyChanged("Value");
                }
            }
            public string Tooltip
            {
                get
                {
                    return tooltip;
                }
                set
                {
                    var changed = this.tooltip != value;
                    this.tooltip = value;
                    if (changed)
                    {
                        RaisePropertyChanged("Tooltip");
                        RaisePropertyChanged("TooltipVisibility");
                    }
                }
            }
    
            public Visibility TooltipVisibility
            {
                get { return string.IsNullOrEmpty(tooltip) ? Visibility.Collapsed : Visibility.Visible; }
            }
    
            public event PropertyChangedEventHandler PropertyChanged = delegate { };
    
            public void SetCellValue(string newValue)
            {
                spreadsheet.SetCellValue(reference, newValue);
            }
    
            private void RaisePropertyChanged(string name)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }
    }
    
  5. 在 Silverlight 控件项目中,请打开 MainPage.xaml,它声明主电子表格的 UI 布局。在 MainPage.xaml 中,将以下 XAML 代码粘贴到现有的网格元素。

    <TextBlock Text="{Binding SourceCode}" FontSize="20" FontFamily="Consolas" Foreground="LightGray"/>
        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
          <StackPanel.Resources>
            <Style x:Key="CellBorder" TargetType="Border">
              <Setter Property="BorderThickness" Value="0.5"/>
              <Setter Property="BorderBrush" Value="LightGray"/>
            </Style>
            <Style x:Key="CaptionBorder" TargetType="Border" BasedOn="{StaticResource CellBorder}">
              <Setter Property="Background" Value="LightBlue"/>
            </Style>
            <Style x:Key="TextContainer" TargetType="TextBlock">
              <Setter Property="FontSize" Value="26"/>
              <Setter Property="FontFamily" Value="Segoe UI"/>
              <Setter Property="Width" Value="200"/>
              <Setter Property="Height" Value="60"/>
            </Style>
    
            <Style x:Key="CaptionText" TargetType="TextBlock" BasedOn="{StaticResource TextContainer}">
              <Setter Property="TextAlignment" Value="Center"/>
              <Setter Property="Foreground" Value="DimGray"/>
            </Style>
            <Style x:Key="ValueEditor" TargetType="TextBox">
              <Setter Property="Width" Value="200"/>
              <Setter Property="Height" Value="60"/>
              <Setter Property="FontSize" Value="26"/>
              <Setter Property="FontFamily" Value="Segoe UI"/>
    
            </Style>
            <Style x:Key="ValueText" TargetType="TextBlock" BasedOn="{StaticResource TextContainer}">
              <Setter Property="TextAlignment" Value="Center"/>
              <Setter Property="VerticalAlignment" Value="Center"/>
              <Setter Property="Foreground" Value="Black"/>
            </Style>
    
          </StackPanel.Resources>
          <Border Style="{StaticResource CellBorder}">
            <StackPanel>
    
              <ItemsControl ItemsSource="{Binding Headers}">
                <ItemsControl.ItemsPanel>
                  <ItemsPanelTemplate>
                    <VirtualizingStackPanel Orientation="Horizontal" />
                  </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                <ItemsControl.ItemTemplate>
                  <DataTemplate>
                    <Border Style="{StaticResource CaptionBorder}">
                      <TextBlock Text="{Binding}" Style="{StaticResource CaptionText}"/>
                    </Border>
                  </DataTemplate>
                </ItemsControl.ItemTemplate>
              </ItemsControl>
    
              <ItemsControl ItemsSource="{Binding Rows}">
                <ItemsControl.ItemTemplate>
                  <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                      <Border Style="{StaticResource CaptionBorder}">
                        <TextBlock Text="{Binding Index}" Style="{StaticResource CaptionText}"/>
                      </Border>
                      <ItemsControl ItemsSource="{Binding Cells}">
                        <ItemsControl.ItemsPanel>
                          <ItemsPanelTemplate>
                            <VirtualizingStackPanel  Orientation="Horizontal"/>
                          </ItemsPanelTemplate>
                        </ItemsControl.ItemsPanel>
                        <ItemsControl.ItemTemplate>
                          <DataTemplate>
                            <Border Style="{StaticResource CellBorder}">
                              <Grid>
                                <TextBox
                                  Name="editor"
                                  Tag="{Binding ElementName=textContainer}"
                                  Visibility="Collapsed"
                                  LostFocus="OnLostFocus"
                                  KeyUp="OnKeyUp"
                                  Text ="{Binding RawValue}"
                                  Style="{StaticResource ValueEditor}"/>
                                <TextBlock
                                  Name="textContainer"
                                  Tag="{Binding ElementName=editor}"
                                  Visibility="Visible"
                                  Text="{Binding Value}"
                                  Style="{StaticResource ValueText}"
                                  MouseLeftButtonDown="OnMouseLeftButtonDown"
                                  ToolTipService.Placement="Mouse">
                                  <ToolTipService.ToolTip>
                                    <ToolTip Visibility="{Binding TooltipVisibility}">
                                      <TextBlock Text="{Binding Tooltip}" Style="{StaticResource TextContainer}" Visibility="{Binding TooltipVisibility}"/>
                                    </ToolTip>
                                  </ToolTipService.ToolTip>
                                </TextBlock>
                              </Grid>
                            </Border>
                          </DataTemplate>
                        </ItemsControl.ItemTemplate>
                      </ItemsControl>
                    </StackPanel>
                  </DataTemplate>
                </ItemsControl.ItemTemplate>
              </ItemsControl>
    
            </StackPanel>
          </Border>
        </StackPanel>
    
  6. 在 MainPage.xaml.cs 中,向使用指令列表添加 using SilverlightFrontEnd;,然后将以下方法添加到 SilverlightApplication1 类。

            void OnLostFocus(object sender, RoutedEventArgs e)
            {
                var editor = (TextBox)e.OriginalSource;
                var text = editor.Text;
    
                HideEditor(e);
    
                EditValue(editor.DataContext, text);
            }
    
            void OnKeyUp(object sender, KeyEventArgs e)
            {
                if (e.Key == Key.Escape)
                {
                    HideEditor(e);
                    e.Handled = true;
                    return;
                }
                else if (e.Key == Key.Enter)
                {
                    var editor = (TextBox)e.OriginalSource;
                    var text = editor.Text;
    
                    HideEditor(e);
    
                    EditValue(editor.DataContext, text);
                    e.Handled = true;
                }
            }
    
            private void EditValue(object dataContext, string newText)
            {
                var cvm = (CellViewModel)dataContext;
                cvm.SetCellValue(newText);
            }
    
            private void OnMouseLeftButtonDown(object sender, RoutedEventArgs e)
            {
                var textBlock = (TextBlock)e.OriginalSource;
                var editor = (TextBox)textBlock.Tag;
                textBlock.Visibility = Visibility.Collapsed;
                editor.Visibility = Visibility.Visible;
                editor.Focus();
            }
    
            private void HideEditor(RoutedEventArgs e)
            {
                var editor = (TextBox)e.OriginalSource;
                var textBlock = (TextBlock)editor.Tag;
                editor.Visibility = Visibility.Collapsed;
                textBlock.Visibility = Visibility.Visible;
            }
    
  7. 在 App.xaml.cs 中,将下面正在使用的指令添加到其中:

    using SilverlightFrontEnd;
    using Portable.Samples.Spreadsheet;
    

    将下面的代码粘贴到 Application_Startup 事件处理程序中:

                var spreadsheet = new Spreadsheet(5, 5);
                var spreadsheetViewModel = new SpreadsheetViewModel(spreadsheet);
                var main = new MainPage();
                main.DataContext = spreadsheetViewModel;
                this.RootVisual = main;
    
  8. 您可以通过直接启动 Silverlight 项目或通过启用承载 Silverlight 控件的 ASP.NET Web 应用程序,测试您的 Silverlight 前端。打开其中一个项目的节点的快捷菜单,然后选择**“设为启动项目”**。

如何:创建使用 F# 可移植库的 Windows 应用商店 应用程序

  1. 在本节中,将创建将 F# 电子表格代码用作其计算组件的 Windows 应用商店 应用程序。在菜单栏上,选择**“文件”菜单上,“添加”“新建项目”。此时将出现“新建项目”对话框。在“已安装”下,展开“Visual C#”,展开“Windows 应用商店”,然后选择“空白应用程序”模板。将新项目命名为 NewFrontEnd,并选择“确定”**按钮。如果已对开发人员许可证提示要创建 Windows 应用商店 应用程序,请输入您的凭据。如果没有凭据,您可以了解在 here 上如何设置对它们进行设置。

    该项目已创建。注意此项目的配置和内容。默认引用包括适用于 Windows 应用商店应用程序的 .NET(其为与 Windows 应用商店 应用程序兼容的 .NET Framework 的子集)和 Windows 程序集(其包括适用于 Windows 运行时的 API 和适用于 Windows 应用商店 应用程序的 UI)。已创建资产和公共子文件夹。资产子文件夹包含适用于 Windows 应用商店 应用程序的几个图标,并且公共子文件夹包含用作 Windows 应用商店 应用程序模板使用的共享例程。默认项目模板也已创建 App.xaml、BlankPage.xaml 及其关联的 C# 代码隐藏文件、App.xaml.cs 和 BlankPage.xaml.cs。App.xaml 描述整个应用程序,BlankPage.xaml 描述其已定义的一个 UI 图面。最后,任何 .pfx 文件及 .appxmanifest 文件均支持 Windows 应用商店 应用程序的安全及部署模型。

  2. 通过打开 Silverlight 项目的引用节点的快捷菜单并选择 “添加引用”,添加引用到电子表格项目。在“引用管理器”中,展开“解决方案”节点,并选择“电子表格”项目,然后选择**“确定”**按钮。

  3. 您将需要已在 Silverlight 项目中使用的某些代码以便支持 Windows 应用商店 应用程序的 UI 的代码。此代码在 ViewModels.cs 中。打开 NewFrontEnd 的项目节点的快捷菜单,选择**“添加”**,然后选择 “新项目”。添加一个 C# 代码文件,并将其命名为 ViewModels.cs。从 Silverlight 项目中的 ViewModels.cs 粘贴代码,然后在此文件的顶部更改 using 指令的块。移除用作 Silverlight UI 的 System.Windows,并添加用作 Windows 应用商店 应用程序的 UI 的 Windows.UI.Xaml 和 Windows.Foundation.Collections。Silverlight 和 Windows 应用商店 UI 都基于 WPF,因此它们互相兼容。使用指令的更新块应与下面的示例类似:

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Input;
    using Portable.Samples.Spreadsheet;
    using Windows.Foundation.Collections;
    using Windows.UI.Xaml;
    

    此外,在 ViewModels.cs 中将命名空间从 SilverlightFrontEnd 更改为 NewFrontEnd。

    您可以重用 ViewModels.cs 中的代码的其余部分,但是可见性之类的某些类型,现为 Windows 应用商店 应用程序(而非 Silverlight)的版本。

  4. 在 Windows 应用商店 应用程序中,App.xaml.cs 代码文件必须具有类似的启动代码,因为其将出现在Silverlight 应用程序的 Application_Startup 事件处理程序中。在 Windows 应用商店 应用程序中,此代码显示在应用程序类的OnLaunched 事件处理程序中。将下面的代码添加到 App.xaml.cs 中的 OnLaunched 事件处理程序中。

    var spreadsheet = new Spreadsheet(5, 5);
    var spreadsheetViewModel = new SpreadSheetViewModel(spreadsheet);
    
  5. 添加电子表格代码的 using 指令。

    using Portable.Samples.Spreadsheet;
    
  6. 在 App.xaml.cs 中,OnLaunched 包含指定要加载的内容的代码。在用户启动应用程序时将添加该应用程序要加载的页。更改 OnLaunched 中的代码以导航到第一页上,如下面的示例所示:

    // Create a frame, and navigate to the first page.
    var rootFrame = new Frame();
    rootFrame.Navigate(typeof(ItemsPage1), spreadsheetViewModel);
    

    您可以删除 BlankPage1.xaml 和代码隐藏文件,因为它们不用于此示例。

  7. 打开 NewFrontEnd 的项目节点的快捷菜单,选择**“添加”**,然后选择 “新项目”。添加项页,并保留默认名称 ItemsPage1.xaml。此步骤会向 ItemsPage1.xaml 和其代码隐藏文件添加 ItemsPage1.xaml.cs 到项目。ItemsPage1.xaml 以具有许多特性的主要 common:LayoutAwarePage 标记开头,下如以下 XAML 代码所示:

    <common:LayoutAwarePage
        x:Name="pageRoot"
        x:Class="NewFrontEnd.ItemsPage1"
        xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:NewFrontEnd"
        xmlns:common="using:NewFrontEnd.Common"
        xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d">
    

    Metro 风格的应用程序的 UI 与创建的 Windows 应用商店 应用程序的 UI 相同,并且 XMAL 格式在这种情况下相同。因此,您可以重用从 MainPage.xaml 中的 XAML 在I temsPage1.xaml 的 Silverlight 项目在 Windows 应用商店 应用程序的 UI。

  8. 复制 Silverlight 项目的 MainPage.xaml 的顶级网格元素中的代码,并为 Windows 应用商店 应用程序的 UI 将其粘贴到项目中的 ItemsPage1.xaml 的顶级网格元素中。当您粘贴代码时,可以覆盖栅格元素的所有现有内容。在网格元素中将背景特性更改为“白色”,并替换 MouseLeftButtonDownPointerPressed

    此事件的名称与 Silverlight 应用程序和 Windows 应用商店 应用程序中的名称不同。

  9. 在 ItemsPage.xaml.cs 中,通过更改 DataContext 方法设置 OnNavigatedTo 属性。

    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        this.DataContext = e.Parameter;
    }
    
  10. 复制下面的事件处理程序代码,然后将其粘贴到 ItemsPage1 类中:OnLostFocusOnKeyUpEditValueOnPointerPressedHideEditor

    void OnLostFocus(object sender, RoutedEventArgs e)
            {
                var editor = (TextBox)e.OriginalSource;
                var text = editor.Text;
    
                HideEditor(e);
    
                EditValue(editor.DataContext, text);
            }
    
            void OnKeyUp(object sender, KeyEventArgs e)
            {
                if (e.Key == Windows.System.VirtualKey.Escape)
                {
                    HideEditor(e);
                    e.Handled = true;
                    return;
                }
                else if (e.Key == Windows.System.VirtualKey.Enter)
                {
                    var editor = (TextBox)e.OriginalSource;
                    var text = editor.Text;
    
                    HideEditor(e);
    
                    EditValue(editor.DataContext, text);
                    e.Handled = true;
                }            
            }
    
            private void EditValue(object dataContext, string newText)
            {
                var cvm = (CellViewModel)dataContext;
                cvm.SetCellValue(newText);
            }
    
            private void OnPointerPressed(object sender, RoutedEventArgs e)
            {
                var textBlock = (TextBlock)e.OriginalSource;
                var editor = (TextBox)textBlock.Tag;
                textBlock.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
                editor.Visibility = Windows.UI.Xaml.Visibility.Visible;
    
                editor.Focus(FocusState.Programmatic);
            }
    
            private void HideEditor(RoutedEventArgs e)
            {
                var editor = (TextBox)e.OriginalSource;
                var textBlock = (TextBlock)editor.Tag;
                editor.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
                textBlock.Visibility = Windows.UI.Xaml.Visibility.Visible;
            }
    
  11. 将启动项目更改您的 Windows 应用商店 的应用程序的项目。打开 NewFrontEnd 项目节点的快捷菜单,选择**“设置为启动项目”**,然后选择 F5 键运行项目。

以使用 F# 的 C#创建可移植库

前面的示例重复代码因为 ViewModels.cs 代码显示在多个项目中。在本节中,创建 C# 可移植库项目以包含此代码。某些情况下,当其使用使用 F# 的可移植库时,您必须身影应用程序的配置文件添加信息。在这种情况下,针对 .NET framework 4.5 的桌面版本的桌面应用程序将引用 C# 可移植库,该库又会引用 F# 可移植库。在这样的情况下,必须将绑定重定向添加到主应用程序的 app.config 文件中。必须添加此重定向,因为只加载了 FSharp.Core 库的一个版本,但是可移植库引用了 .NET 可移植版本。对 FSharp.Core 函数的 .NET Portable 版本的任何调用都必须重定向到在桌面应用程序中加载的 FSharp.Core 的唯一版本。绑定重定向仅在桌面应用程序中有必要,因为 Silverlight 5 和 Windows 应用商店 应用程序的运行时环境使用 .NET 可移植版本的 FSharp.Core,而不是完整的桌面版本。

如何:创建引用使用 F# 的可移植库的桌面应用程序

  1. 在菜单栏上,选择**“文件”菜单上,“添加”“新建项目”。在“已安装”**下,展开 **“visual C#”节点,选择“.NET 可移植库”**项目模板,然后将项目命名为 ViewModels。

  2. 您必须设置此 .NET 可移植库的目标,以便 F# 可移植库与您将引用添加到的相匹配。否则,错误消息将通知您不匹配。在 ViewModels 项目的快捷菜单上,选择**“属性”。在“库”**选项上,更改此可移植库的目标以与 .NET framework 4.5、Silverlight 5 和 Windows 应用商店 应用程序匹配。

  3. 在**“引用”的节点的快捷菜单上,选择“添加引用”。在“解决方案”**下,应选择电子表格旁边的复选框。

  4. 从某一其他项目复制 ViewModels.cs 的代码,并将其粘贴到 ViewModels 项目的代码文件中。

  5. 进行以下更改,在 ViewModels 使代码完全独立 UI 平台:

    1. 移除 System.Windows、System.Windows.Input、Windows.Foundation.Collections 和 Windows.UI.Xaml(如果存在)的 using 指令。

    2. 将命名空间更改为 ViewModels。

    3. 移除 TooltipVisibility 属性。此属性用于平台相关对象的可见性。

  6. 在菜单栏上,选择**“文件”菜单上,“添加”“新建项目”。在“已安装”下,展开“visual C#”节点,然后选择“WPF应用程序”项目模板。将新项目命名为 WorkflowDurationTracing,并选择“确定”**按钮。

  7. 打开桌面项目中**“引用”节点的快捷菜单,然后选择“添加引用”。在“解决方案”**下,选择电子表格和 ViewModels 项目。

  8. 打开 WPF 应用程序的 app.config 文件,然后添加如下代码行。此代码配置相应的绑定重定向应用,这些扩展在面向 .NET framework 4.5 的桌面应用程序引用使用 F# 的 .NET 可移植库。.NET 可移植库使用 2.3.5.0 版本的 FSharp.Core 库,并且 .NET Framework 4.5 桌面应用程序使用版本 4.3.0.0。

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
        <startup> 
            <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
        </startup>
        <runtime>
            <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
                <dependentAssembly>
                    <assemblyIdentity name="FSharp.Core" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
                    <bindingRedirect oldVersion="2.3.5.0" newVersion="4.3.0.0"/>
                </dependentAssembly>
            </assemblyBinding>
        </runtime>
    </configuration>
    

    现在,您必须向可移植版本的 F# Core 库添加引用。此引用需要,只要您有可移植库引用 F# 可移植库的应用程序。

  9. 打开桌面项目中**“引用”节点的快捷菜单,然后选择“添加引用”。选择“浏览”**,然后导航到安装 Visual Studio 的 Program Files 文件夹下的 Reference Assemblies\Microsoft\FSharp\3.0\Runtime\.NETPortable\FSharp.Core.dll。

  10. 在“桌面”项目中,请将 ViewModels.cs 和 Portable.Samples.Spreadsheet 的使用指令添加到App.xaml.cs 和 MainWindow.xaml.cs。

    using ViewModels;
    using Portable.Samples.Spreadsheet;
    
  11. 打开 MainWindow.xaml 文件,然后将窗口类的标题属性更改为 Spreadsheet。

  12. 复制 Silverlight 项目中 MainPage.xaml 的网格元素内的代码,并将该代码粘贴到桌面项目中的 MainWindow.xaml 的网格元素中。

  13. 复制 Silverlight 项目中 MainPage.xaml.cs 中的事件处理代码,并将该代码粘贴到桌面项目中的 MainWindow.xaml.cs 的网格元素中。

            void OnLostFocus(object sender, RoutedEventArgs e)
            {
                var editor = (TextBox)e.OriginalSource;
                var text = editor.Text;
    
                HideEditor(e);
    
                EditValue(editor.DataContext, text);
            }
    
            void OnKeyUp(object sender, KeyEventArgs e)
            {
                if (e.Key == Key.Escape)
                {
                    HideEditor(e);
                    e.Handled = true;
                    return;
                }
                else if (e.Key == Key.Enter)
                {
                    var editor = (TextBox)e.OriginalSource;
                    var text = editor.Text;
    
                    HideEditor(e);
    
                    EditValue(editor.DataContext, text);
                    e.Handled = true;
                }
            }
    
            private void EditValue(object dataContext, string newText)
            {
                var cvm = (CellViewModel)dataContext;
                cvm.SetCellValue(newText);
            }
    
            private void OnMouseLeftButtonDown(object sender, RoutedEventArgs e)
            {
                var textBlock = (TextBlock)e.OriginalSource;
                var editor = (TextBox)textBlock.Tag;
                textBlock.Visibility = Visibility.Collapsed;
                editor.Visibility = Visibility.Visible;
                editor.Focus();
            }
    
            private void HideEditor(RoutedEventArgs e)
            {
                var editor = (TextBox)e.OriginalSource;
                var textBlock = (TextBlock)editor.Tag;
                editor.Visibility = Visibility.Collapsed;
                textBlock.Visibility = Visibility.Visible;
            }
    
  14. 添加电子表格启动代码到 MainWindow.xaml.cs 中的 MainWindow 构造函数,并且替换对引用了 MainWindow 的 MainPage 的引用。

        public MainWindow()
        {
                var spreadsheet = new Spreadsheet(5, 5);
                var spreadsheetViewModel = new SpreadsheetViewModel(spreadsheet);
    
    
                this.DataContext = spreadsheetViewModel;
                InitializeComponent();
        }
    
  15. 打开桌面项目的快捷菜单,然后选择**“设为启动项目”**。

  16. 选择 F5 键生成该应用程序,然后对其进行调试。

后续步骤

或者,可以为 Windows 应用商店 应用程序和 Silverlight 应用程序修改,以使用新的 ViewModels 可移植库。

继续了解 Windows 应用商店 应用程序,网址为 Windows 开发人员中心

请参见

概念

Windows 应用商店应用程序

使用 .NET Framework 的跨平台开发

其他资源

Visual F# 示例和演练

Silverlight