Tutorial: Crear una biblioteca portable de F#

Mediante este tutorial puede crear un ensamblado en F# que puede usar con una aplicación de Silverlight, con una aplicación de escritorio tradicional o en una aplicación Tienda Windows que cree mediante API de .NET.De esta forma, puede escribir la parte de la interfaz de usuario de la aplicación en otro lenguaje .NET, como por ejemplo C# o Visual Basic, y la parte del algoritmo en F#.También puede admitir diferentes interfaces de usuario para distintas plataformas de destino.

No puede usar la interfaz de usuario de Tienda Windows directamente desde F#, por lo que es recomendable que escriba dicha interfaz para su aplicación Tienda Windows en otro lenguaje .NET y el código de F# en una biblioteca portable.Puede escribir la interfaz de usuario de Silverlight y Windows Presentation Foundation (WPF) directamente en F# pero puede que quiera aprovechar las herramientas de diseño adicionales disponibles cuando escribe código de C# o Visual Basic en Visual Studio.

Requisitos previos

Para crear una aplicación Tienda Windows, debe tener Windows 8 en su equipo de desarrollo.

Para crear un proyecto de Silverlight, debe tener Silverlight 5 en su equipo de desarrollo.

La aplicación Spreadsheet

En este tutorial desarrollará una hoja de cálculo sencilla que presenta una cuadrícula al usuario y acepta entradas numéricas y fórmulas en sus celdas.El nivel F# procesa y valida todas las entradas, y muy especialmente, analiza el texto de las fórmulas y calcula los resultados de las mismas.En primer lugar, creará un código para el algoritmo en F#, que incluye código para analizar expresiones en las que puede haber referencias, números y operadores matemáticos.Esta aplicación también incluye el código para controlar qué celdas se deben actualizar cuando un usuario actualiza el contenido de otra celda.A continuación, creará las interfaces de usuario.

En la siguiente figura se muestra la aplicación que creará en este tutorial.

Interfaz de usuario de la aplicación Spreadsheet

Captura de pantalla de aplicación final del tutorial para crear bibliotecas portables de F#

Este tutorial contiene las siguientes secciones.

  • 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#

Cómo: Crear una biblioteca portátil de F#

  1. En la barra de menús, elija Archivo, Nuevo proyecto.En el cuadro de diálogo Nuevo proyecto, expanda Visual F#, elija el tipo de proyecto Biblioteca portable de F# y, a continuación, denomine a dicha biblioteca Spreadsheet.Tenga en cuenta que el proyecto hace referencia a una versión especial de FSharp.Core.

  2. En el Explorador de soluciones, expanda el nodo References y, a continuación, seleccione el nodo FSharp.Core.En la ventana Propiedades, el valor de la propiedad FullPath debe contener .NETPortable, lo que indica que está usando la versión portable de la biblioteca básica de F#.También puede revisar las distintas bibliotecas de .NET a las que puede obtener acceso de forma predeterminada.Todas estas bibliotecas trabajan con un subconjunto común de .NET Framework definido como .NET portable.Puede quitar referencias que no necesite, pero si agrega referencias, el ensamblado de referencia debe estar disponible en todas las plataformas de destino.La documentación para un ensamblado normalmente indica las plataformas en las que está disponible.

  3. Abra el menú contextual del proyecto y, a continuación, elija Propiedades.En la pestaña Aplicación, la versión de .NET Framework de destino se establece en Subconjunto portable de .NET.Para Visual Studio 2012, el destino de este subconjunto es .NET para aplicaciones Tienda Windows, .NET Framework 4.5 y Silverlight 5.Esta configuración es importante porque, como biblioteca portable, la aplicación se debe ejecutar en el runtime disponible en varias plataformas.Los runtime para aplicaciones Tienda Windows y Silverlight 5 contienen subconjuntos de .NET Framework completo.

  4. Cambie el nombre del archivo de código principal Spreadsheet.fs y, a continuación, pegue el siguiente código en la ventana del editor.Este código define la funcionalidad de una hoja de cálculo básica.

    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 |]
    

Cómo: Crear una aplicación de Silverlight que use una biblioteca portable de F#

  1. En la barra de menús, elija Archivo, Agregar y, a continuación, Nuevo proyecto.En el cuadro de diálogo Agregar nuevo proyecto, expanda Visual C#, expanda Silverlight y, a continuación, elija Aplicación de Silverlight.Aparece el cuadro de diálogo Nueva aplicación de Silverlight.

  2. Asegúrese de que la casilla Hospedar la aplicación de Silverlight en un nuevo sitio web está activada y, en el cuadro desplegable, asegúrese de que está seleccionada la opción Proyecto de aplicación web ASP.NET. Por último, elija el botón Aceptar.Se crearán dos proyectos: uno tiene el control de Silverlight y el otro es una aplicación web ASP.NET que hospeda el control.

  3. Agregue una referencia al proyecto Spreadsheet.Abra el menú contextual del nodo References del proyecto de Silverlight y, a continuación, elija Agregar referencia.Aparecerá el Administrador de referencias.Expanda el nodo Solución y elija el proyecto Spreadsheet. A continuación, elija el botón Aceptar.

  4. En este paso, creará un modelo de vista que describe todo lo que la interfaz de usuario debe hacer sin describir cómo aparece.Abra el menú contextual para el nodo del proyecto, elija Agregar y, a continuación, elija Nuevo elemento.Agregue un archivo de código, denomínelo ViewModel.cs y, a continuación, pegue el siguiente código en él:

    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. En el proyecto de control de Silverlight, abra MainPage.xaml, que declara el diseño de la interfaz de usuario para la hoja de cálculo principal.En MainPage.xaml, pegue el siguiente código XAML en el elemento Grid existente.

    <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. En MainPage.xaml.cs, agregue using SilverlightFrontEnd; a la lista de directivas de uso y, a continuación, agregue los siguientes métodos a la clase 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. En App.xaml.cs, agregue las siguientes directivas de uso:

    using SilverlightFrontEnd;
    using Portable.Samples.Spreadsheet;
    

    Pegue el código siguiente en el controlador de eventos Application_Startup:

                var spreadsheet = new Spreadsheet(5, 5);
                var spreadsheetViewModel = new SpreadsheetViewModel(spreadsheet);
                var main = new MainPage();
                main.DataContext = spreadsheetViewModel;
                this.RootVisual = main;
    
  8. Puede probar su front-end de Silverlight iniciando el proyecto de Silverlight directamente o iniciando la aplicación web ASP.NET que hospeda el control de Silverlight.Abra el menú contextual del nodo de cualquiera de esos proyectos y, a continuación, elija Establecer como proyecto de inicio.

Cómo: Crear una aplicación Tienda Windows que use una biblioteca portable de F#

  1. En esta sección creará una aplicación Tienda Windows que usa el código de la hoja de cálculo de F# como su componente computacional.En la barra de menús, elija Archivo, Agregar y Nuevo proyecto.Aparecerá el cuadro de diálogo Nuevo proyecto.En Instalado, expanda Visual C#, expanda Tienda Windows y, a continuación, elija la plantilla Aplicación vacía.Denomine NewFrontEnd al proyecto y, a continuación, elija el botón Aceptar.Si se le pide la licencia de desarrollador para crear aplicaciones Tienda Windows, escriba las credenciales.Si no tiene credenciales, puede averiguar cómo configurarlas aquí.

    El proyecto se habrá creado.Observe la configuración y el contenido de este proyecto.Las referencias predeterminadas incluyen .NET para aplicaciones de la Tienda Windows, que es el subconjunto de .NET Framework (compatible con aplicaciones Tienda Windows), y el ensamblado de Windows, que incluye las API para Windows en tiempo de ejecución y la interfaz de usuario para aplicaciones Tienda Windows.Las subcarpetas Assets y Common se habrán creado.La subcarpeta Assets contiene varios iconos que se aplican a aplicaciones Tienda Windows y la carpeta Common contiene rutinas compartidas que usan las plantillas para aplicaciones Tienda Windows.La plantilla de proyecto predeterminada también ha creado App.xaml, BlankPage.xaml y sus archivos de código subyacente en C# asociados, App.xaml.cs y BlankPage.xaml.cs.App.xaml describe la aplicación global y BlankPage.xaml describe su superficie de interfaz de usuario definida.Finalmente, todos los archivos .pfx y .appxmanifest admiten los modelos de seguridad y desarrollo para aplicaciones Tienda Windows.

  2. Agregue una referencia al proyecto Spreadsheet abriendo el menú contextual del nodo References del proyecto de Silverlight y eligiendo Agregar referencia.En el Administrador de referencias, expanda el nodo Solución, elija el proyecto Spreadsheet y, a continuación, elija el botón Aceptar.

  3. Necesitará parte del código que ya utilizó en el proyecto de Silverlight para admitir el código para la interfaz de usuario de la aplicación Tienda Windows.Este código se encuentra en ViewModels.cs.Abra el menú contextual para el nodo del proyecto de NewFrontEnd, elija Agregar y, a continuación, elija Nuevo elemento.Agregue un archivo de código de C# y denomínelo ViewModels.cs.Pegue el código de ViewModels.cs en el proyecto Silverlight y, a continuación, cambie el bloque de directivas de uso en la parte superior de este archivo.Quite System.Windows, que se usa para la interfaz de usuario de Silverlight, y agregue Windows.UI.Xaml y Windows.Foundation.Collections, que se usan para la interfaz de usuario de la aplicación Tienda Windows.Tanto Silverlight como la interfaz de usuario Tienda Windows se basan en WPF, por lo que son compatibles entre sí.El bloque actualizado de directivas de uso debe parecerse al siguiente ejemplo:

    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;
    

    Asimismo, cambie el espacio de nombres de ViewModels.cs de SilverlightFrontEnd a NewFrontEnd.

    Puede reusar el resto del código de ViewModels.cs, pero algunos tipos, como Visibility, son ahora las versiones para aplicaciones Tienda Windows en lugar de Silverlight.

  4. En esta aplicación Tienda Windows, el archivo de código App.xaml.cs debe tener código de inicio similar al que apareció en el controlador de eventos Application_Startup para la aplicación de Silverlight.En una aplicación Tienda Windows, este código aparece en el controlador de eventos OnLaunched de la clase App.Agregue el código siguiente al controlador de eventos OnLaunched en App.xaml.cs:

    var spreadsheet = new Spreadsheet(5, 5);
    var spreadsheetViewModel = new SpreadSheetViewModel(spreadsheet);
    
  5. Agregar una directiva de uso para el código de Spreadsheet.

    using Portable.Samples.Spreadsheet;
    
  6. En App.xaml.cs, OnLaunched contiene código que especifica qué página cargar.Agregará una página que desea que la aplicación cargue cuando un usuario la inicie.Cambie el código de OnLaunched para navegar a la primera página, tal y como se muestra en el siguiente ejemplo:

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

    Puede eliminar BlankPage1.xaml y su archivo de código subyacente porque no se usan en este ejemplo.

  7. Abra el menú contextual para el nodo del proyecto de NewFrontEnd, elija Agregar y, a continuación, elija Nuevo elemento.Agregue una página de elementos y conserve el nombre predeterminado ItemsPage1.xaml.Este paso agrega tanto ItemsPage1.xaml como su archivo de código subyacente, ItemsPage1.xaml.cs, al proyecto.ItemsPage1.xaml comienza con una etiqueta principal de common:LayoutAwarePage con muchos atributos, tal y como se muestra en el siguiente código 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">
    

    La interfaz de usuario de la aplicación Tienda Windows es idéntica a la interfaz de usuario de la aplicación de Silverlight que creó y el formato XAML es el mismo en este caso.Por tanto, puede reusar el formato XAML de MainPage.xaml del proyecto de Silverlight para ItemsPage1.xaml en la interfaz de usuario de la aplicación Tienda Windows.

  8. Copie el código que se encuentra dentro del elemento Grid de nivel superior de MainPage.xaml para el proyecto Silverlight y péguelo en el elemento Grid de nivel superior de ItemsPage1.xaml del proyecto de la interfaz de usuario de la aplicación Tienda Windows.Cuando pegue el código, puede sobrescribir cualquier contenido existente del elemento Grid.Cambie el atributo Background del elemento Grid a "White" y reemplace MouseLeftButtonDown por PointerPressed.

    El nombre de este evento es diferente en las aplicaciones de Silverlight y en las aplicaciones Tienda Windows.

  9. En ItemsPage.xaml.cs, establezca la propiedad DataContext cambiando el método OnNavigatedTo.

    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        this.DataContext = e.Parameter;
    }
    
  10. Copie el siguiente código de controlador de eventos y péguelo en la clase ItemsPage1: OnLostFocus, OnKeyUp, EditValue, OnPointerPressed y HideEditor.

    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. Cambie el proyecto de inicio al proyecto de la aplicación Tienda Windows.Abra el menú contextual del nodo de proyecto NewFrontEnd, elija Establecer como proyecto de inicio y, a continuación, presione la tecla F5 para ejecutar el proyecto.

Creación de una biblioteca portable en C# que usa F#

El ejemplo anterior duplica código en el que el código ViewModels.cs aparece en varios proyectos.En esta sección, creará un proyecto Biblioteca portable de C# para que contenga este código.En algunos casos, debe agregar información al archivo de configuración de una aplicación cuando consume bibliotecas portables que usan F#.En este caso, una aplicación de escritorio, cuyo destino es la versión de escritorio de .NET Framework 4.5, hace referencia a una biblioteca portátil de C# que, a su vez, hace referencia a una biblioteca portable de F#.En tal caso, debe agregar una redirección de enlace al archivo app.config de la aplicación principal.Debe agregar esta redirección porque solo se carga una versión de la biblioteca FSharp.Core, pero las bibliotecas portables hacen referencia a la versión .NET portable.Todas las llamadas a las versiones .NET portable de las funciones FSharp.Core se deben redireccionar a la versión única de FSharp.Core que está cargada en una aplicación de escritorio.Las redirecciones de enlace solo son necesarias en la aplicación de escritorio porque los entornos de runtime para Silverlight 5 y aplicaciones Tienda Windows usan la versión .NET portable de FSharp.Core, no la versión de escritorio completa.

Cómo: Crear una aplicación de escritorio que haga referencia a una biblioteca portable que usa F#

  1. En la barra de menús, elija Archivo, Agregar y Nuevo proyecto.En Instalado, expanda el nodo Visual C#, elija la plantilla de proyecto Biblioteca portable de .NET y, a continuación, asigne el nombre ViewModels al proyecto.

  2. Debe establecer los destinos para esta biblioteca portable de .NET para que coincidan con la biblioteca portable de F# a la que agregará una referencia.De lo contrario, un mensaje de error le informará de la discordancia.En el menú contextual del proyecto ViewModels, elija Propiedades.En la pestaña Biblioteca, cambie los destinos para esta biblioteca portable para que coincidan con .NET Framework 4.5, Silverlight 5 y aplicaciones Tienda Windows.

  3. En el menú contextual para el nodo References, elija Agregar referencia.Bajo Solución, active la casilla situada junto a Spreadsheet.

  4. Copie el código para ViewModels.cs de uno de los otros proyectos y péguelo en el archivo de código para el proyecto ViewModels.

  5. Realice los cambios siguientes, mediante los cuales el código de ViewModels queda totalmente independiente de la plataforma de la interfaz de usuario:

    1. Quite directivas de uso para System.Windows, System.Windows.Input, Windows.Foundation.Collections y Windows.UI.Xaml, si existen.

    2. Cambie el espacio de nombres a ViewModels.

    3. Quite la propiedad TooltipVisibility.Esta propiedad utiliza Visibility, que es un objeto dependiente de la plataforma.

  6. En la barra de menús, elija Archivo, Agregar y Nuevo proyecto.En Instalado, expanda el nodo Visual C# y, a continuación, elija la plantilla de proyecto Aplicación WPF.Denomine Desktop al nuevo proyecto y elija el botón Aceptar.

  7. Abra el menú contextual del nodo References del proyecto Desktop y, a continuación, elija Agregar referencia.Bajo Solución, elija los proyectos Spreadsheet y ViewModels.

  8. Abra el archivo app.config para la aplicación WPF y, a continuación, agregue las siguientes líneas de código.Este código configura las redirecciones de enlace adecuadas que se aplican cuando una aplicación de escritorio cuyo destino es .NET Framework 4.5 hace referencia a una biblioteca portable de .NET que usa F#.Las bibliotecas portables de .NET usan la versión 2.3.5.0 de la biblioteca FSharp.Core y las aplicaciones de escritorio de .NET Framework 4.5 usan la versión 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>
    

    Ahora debe agregar una referencia a la versión portable de la biblioteca básica de F#.Esta referencia es obligatoria cuando tiene una aplicación que consume una biblioteca portable que hace referencia a una biblioteca portable de F#.

  9. Abra el menú contextual del nodo References del proyecto Desktop y, a continuación, elija Agregar referencia.Elija Examinar y, a continuación, navegue a Reference Assemblies\Microsoft\FSharp\3.0\Runtime\.NETPortable\FSharp.Core.dll que se encuentra en la carpeta Archivos de programa donde está instalado Visual Studio.

  10. En el proyecto Desktop, agregue directivas de uso para ViewModels.cs y Portable.Samples.Spreadsheet a App.xaml.cs y MainWindow.xaml.cs.

    using ViewModels;
    using Portable.Samples.Spreadsheet;
    
  11. Abra el archivo MainWindow.xaml y, a continuación, cambie el atributo de título de la clase Window a Spreadsheet.

  12. Copie el código que se encuentra dentro del elemento Grid de MainPage.xaml del proyecto de Silverlight y péguelo en el elemento Grid de MainWindow.xaml del proyecto Desktop.

  13. Copie el código de control de eventos de MainPage.xaml.cs del proyecto de Silverlight y péguelo en MainWindow.xaml.cs en el proyecto Desktop.

            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. Agregue el código de inicio de la hoja de cálculo al constructor MainWindow en MainWindow.xaml.cs y reemplace las referencias a MainPage por las referencias a MainWindow.

        public MainWindow()
        {
                var spreadsheet = new Spreadsheet(5, 5);
                var spreadsheetViewModel = new SpreadsheetViewModel(spreadsheet);
    
    
                this.DataContext = spreadsheetViewModel;
                InitializeComponent();
        }
    
  15. Abra el menú contextual del proyecto Desktop y, a continuación, elija Establecer como proyecto de inicio.

  16. Presione la tecla F5 para compilar la aplicación y, a continuación, depúrela.

Pasos siguientes

Como alternativa, puede modificar los proyectos de la aplicación Tienda Windows y de la aplicación de Silverlight para usar la nueva biblioteca portable ViewModels.

Puede obtener más información acerca de las aplicaciones Tienda Windows en el Centro de desarrollo de Windows.

Vea también

Conceptos

Aplicaciones de la Tienda Windows

Desarrollo multiplataforma con .NET Framework

Otros recursos

Tutoriales y ejemplos de Visual F#

Silverlight