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 de la Tienda Windows que cree mediante las API de .NET. De esta forma, puede escribir la parte de la interfaz de usuario de la aplicación en otro lenguaje de .NET, como por ejemplo C# o Visual Basic, y la parte del algoritmo en F#. También puede disponer de distintas interfaces de usuario para varias plataformas de destino.
No puede usar la interfaz de usuario de la Tienda Windows directamente desde F#, por lo que es recomendable que escriba dicha interfaz para su aplicación de la Tienda Windows en otro lenguaje de .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 de la 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. La capa F# procesa y valida todas las entradas y, especialmente, analiza el texto de las fórmulas y calcula los resultados de las mismas. En primer lugar, creará el código para el algoritmo en F#, que incluye código para analizar expresiones en las que puede haber referencias a celdas, 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
Este tutorial contiene las siguientes secciones.
Cómo: Crear una biblioteca portable de F#
Cómo: Crear una aplicación de Silverlight que use una biblioteca portable de F#
Cómo: Crear una aplicación de la Tienda Windows que use una biblioteca portable de F#
Cómo: Crear una aplicación de escritorio que haga referencia a una biblioteca portable que usa F#
Crear una biblioteca portable de F#
Cómo: Crear una biblioteca portable de F#
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, asigne a la biblioteca el nombre Spreadsheet. Tenga en cuenta que el proyecto hace referencia a una versión especial de FSharp.Core.
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 ver las distintas bibliotecas de .NET a las que puede obtener acceso de forma predeterminada. Todas estas bibliotecas funcionan con un subconjunto común de .NET Framework definido como portable de .NET. Puede quitar las 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.
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 está establecida en Subconjunto portable de .NET. Para Visual Studio 2012, el destino de este subconjunto es .NET para aplicaciones de la 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 runtimes para aplicaciones de la Tienda Windows y Silverlight 5 contienen subconjuntos del .NET Framework completo.
Cambie el nombre del archivo de código principal Spreadsheet.fs y, a continuación, pegue el código siguiente 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 |]
Usar una biblioteca portable en una aplicación de Silverlight
Cómo: Crear una aplicación de Silverlight que use una biblioteca portable de F#
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.
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.
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.
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, asígnele el nombre 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)); } } }
En el proyecto del 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>
En MainPage.xaml.cs, agregue using SilverlightFrontEnd; a la lista de directivas Using 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; }
En App.xaml.cs, agregue las siguientes directivas Using:
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;
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.
Usar la biblioteca portable en una aplicación de la Tienda Windows
Cómo: Crear una aplicación de la Tienda Windows que use una biblioteca portable de F#
En esta sección creará una aplicación de la Tienda Windows que usa el código de F# de la hoja de cálculo como componente de computación. En la barra de menús, elija Archivo, Agregar, 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. Asigne el nombre NewFrontEnd al proyecto y, a continuación, elija el botón Aceptar. Si se le pide la licencia de desarrollador para crear aplicaciones de la Tienda Windows, escriba sus credenciales. Si no tiene credenciales, puede averiguar cómo configurarlas aquí.
Se crea el proyecto. 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 de la Tienda Windows, y el ensamblado de Windows, que incluye las API para Windows en tiempo de ejecución y la interfaz de usuario para las aplicaciones de la Tienda Windows. Se crean las subcarpetas Assets y Common. La subcarpeta Assets contiene varios iconos que se aplican a aplicaciones de la Tienda Windows, y la subcarpeta Common contiene rutinas compartidas que usan las plantillas para aplicaciones de la 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, los archivos .pfx y .appxmanifest contienen los modelos de seguridad e implementación para las aplicaciones de la Tienda Windows.
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.
Necesitará parte del código que ya utilizó en el proyecto de Silverlight para el código para la interfaz de usuario de la aplicación de la Tienda Windows. Este código se encuentra en ViewModels.cs. Abra el menú contextual para el nodo del proyecto NewFrontEnd, elija Agregar y, a continuación, elija Nuevo elemento. Agregue un archivo de código de C# y asígnele el nombre ViewModels.cs. Pegue el código de ViewModels.cs del proyecto de Silverlight y, a continuación, cambie el bloque de directivas Using del principio 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 de la Tienda Windows. Tanto Silverlight como la interfaz de usuario de la Tienda Windows se basan en WPF, por lo que son compatibles entre sí. El bloque actualizado de directivas Using debe parecerse al del ejemplo siguiente:
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 reutilizar el resto del código de ViewModels.cs, pero algunos tipos, como Visibility, son ahora las versiones para las aplicaciones de la Tienda Windows en lugar de Silverlight.
En esta aplicación de la Tienda Windows, el archivo de código App.xaml.cs debe tener código de inicio similar al que aparecía en el controlador de eventos Application_Startup de la aplicación de Silverlight. En una aplicación de la 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);
Agregue una directiva Using para el código de Spreadsheet.
using Portable.Samples.Spreadsheet;
En App.xaml.cs, OnLaunched contiene código que especifica qué página se debe cargar. Agregará la página que desea que se cargue cuando un usuario inicie la aplicación. Cambie el código de OnLaunched para navegar a la primera página, tal como se muestra en el ejemplo siguiente:
// 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.
Abra el menú contextual para el nodo del proyecto 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 common:LayoutAwarePage con muchos atributos, tal 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 de la 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 reutilizar el formato XAML de MainPage.xaml del proyecto de Silverlight para ItemsPage1.xaml en la interfaz de usuario de la aplicación de la Tienda Windows.
Copie el código que se encuentra dentro del elemento Grid de nivel superior de MainPage.xaml para el proyecto de 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 de la 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 de la Tienda Windows.
En ItemsPage.xaml.cs, establezca la propiedad DataContext cambiando el método OnNavigatedTo.
protected override void OnNavigatedTo(NavigationEventArgs e) { this.DataContext = e.Parameter; }
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; }
Cambie el proyecto de inicio al proyecto de la aplicación de la Tienda Windows. Abra el menú contextual del nodo del proyecto NewFrontEnd, elija Establecer como proyecto de inicio y, a continuación, presione la tecla F5 para ejecutar el proyecto.
Crear una biblioteca portable en C# que usa F#
En el ejemplo anterior hay código repetido, ya que el código de ViewModels.cs aparece en varios proyectos. En esta sección, creará un proyecto de 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 que consuma bibliotecas portables que usen 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 portable 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 portable de .NET. Todas las llamadas a las versiones portables de .NET de las funciones de 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 tiempo de ejecución para las aplicaciones de la Tienda Windows y de Silverlight 5 usan la versión portable de .NET 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#
En la barra de menús, elija Archivo, Agregar, 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.
Debe establecer los destinos de 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 de esta biblioteca portable para que coincidan con las aplicaciones de la Tienda Windows, .NET Framework 4.5 y Silverlight 5.
En el menú contextual del nodo References, elija Agregar referencia. En Solución, active la casilla situada junto a Spreadsheet.
Copie el código de ViewModels.cs de uno de los otros proyectos y péguelo en el archivo de código del proyecto ViewModels.
Realice los cambios siguientes, tras los cuales el código de ViewModels será totalmente independiente de la plataforma de la interfaz de usuario:
Quite las directivas Using para System.Windows, System.Windows.Input, Windows.Foundation.Collections y Windows.UI.Xaml, si existen.
Cambie el espacio de nombres a ViewModels.
Quite la propiedad TooltipVisibility. Esta propiedad utiliza Visibility, que es un objeto dependiente de la plataforma.
En la barra de menús, elija Archivo, Agregar, Nuevo proyecto. En Instalado, expanda el nodo Visual C# y, a continuación, elija la plantilla de proyecto Aplicación WPF. Asigne el nombre Desktop al nuevo proyecto y elija el botón Aceptar.
Abra el menú contextual del nodo References del proyecto Desktop y, a continuación, elija Agregar referencia. En Solución, elija los proyectos Spreadsheet y ViewModels.
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 principal de F#. Esta referencia es obligatoria cuando se tiene una aplicación que consume una biblioteca portable que hace referencia a una biblioteca portable de F#.
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.
En el proyecto Desktop, agregue directivas Using para ViewModels.cs y Portable.Samples.Spreadsheet a App.xaml.cs y MainWindow.xaml.cs.
using ViewModels; using Portable.Samples.Spreadsheet;
Abra el archivo MainWindow.xaml y, a continuación, cambie el atributo de título de la clase Window a Spreadsheet.
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.
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; }
Agregue el código de inicio de la hoja de cálculo al constructor MainWindow de MainWindow.xaml.cs y reemplace las referencias a MainPage por referencias a MainWindow.
public MainWindow() { var spreadsheet = new Spreadsheet(5, 5); var spreadsheetViewModel = new SpreadsheetViewModel(spreadsheet); this.DataContext = spreadsheetViewModel; InitializeComponent(); }
Abra el menú contextual del proyecto Desktop y, a continuación, elija Establecer como proyecto de inicio.
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 de la Tienda Windows y de la aplicación de Silverlight para que usen la nueva biblioteca portable ViewModels.
Obtenga más información sobre las aplicaciones de la Tienda Windows en Centro para desarrolladores de Windows.
Vea también
Conceptos
Aplicaciones de la Tienda Windows
Desarrollo multiplataforma con la Biblioteca de clases portable