Partager via


Procédure pas à pas : création d'une bibliothèque F# portable

Grâce à cette procédure pas à pas, vous pouvez créer un assembly en F# que vous pouvez utiliser avec une application Silverlight, une application de bureau classique ou dans une application Windows Store créée à l'aide des API .NET. Ainsi, vous pouvez écrire la partie IU de votre application dans un autre langage .NET, tel que C# ou Visual Basic, et la partie algorithmique en F#. Vous pouvez également prendre en charge différentes interfaces utilisateur qui ciblent différentes plateformes.

Dans la mesure où vous ne pouvez pas utiliser l'interface utilisateur Windows Store directement en F#, nous vous recommandons d'écrire l'interface utilisateur pour votre application Windows Store dans un autre langage .NET et le code F# dans une bibliothèque portable. Vous pouvez écrire les IU Silverlight et Windows Presentation Foundation (WPF) en F# directement, mais vous souhaitez peut-être tirer parti des outils de conception supplémentaires qui sont disponibles lorsque vous écrivez du code C# ou Visual Basic dans Visual Studio.

Composants requis

Pour pouvoir créer une application Windows Store, Windows 8 doit être installé sur votre ordinateur de développement.

Pour pouvoir créer un projet Silverlight, Silverlight 5 doit être installé sur votre ordinateur de développement.

Application Feuille de calcul

Dans cette procédure pas à pas, vous développez une feuille de calcul simple qui présente une grille à l'utilisateur et accepte les entrées numériques et les formules dans ces cellules. La couche F# traite et valide toutes les entrées ; elle analyse notamment le texte des formules et calcule les résultats des formules. Vous commencez par créer le code algorithmique F#, qui contient du code pour analyser les expressions impliquant des références de cellules, des nombres et des opérateurs mathématiques. Cette application contient également le code pour suivre les cellules à mettre à jour lorsqu'un utilisateur met à jour le contenu d'une autre cellule. Ensuite, vous créez les interfaces utilisateur.

La figure suivante affiche l'application que vous allez créer dans cette procédure pas à pas.

Interface utilisateur de l'application Feuille de calcul

Capture d'écran de la procédure pas à pas portable F# de l'application finale

Cette procédure pas-à-pas contient les sections suivantes.

  • Procédure : créer une bibliothèque portable F#

  • Procédure : créer une application Silverlight qui utilise une bibliothèque portable F#

  • Procédure : créer une application Windows Store qui utilise une bibliothèque portable F#

  • Procédure : créer une application de bureau qui référence une bibliothèque portable utilisant F#

Création d'une bibliothèque F# portable

Procédure : créer une bibliothèque portable F#

  1. Dans la barre de menus, cliquez sur Fichier, Nouveau projet. Dans la boîte de dialogue Nouveau projet, développez Visual F#, sélectionnez le type de projet Bibliothèque portable F#, puis attribuez à la bibliothèque le nom Feuille de calcul. Notez que le projet référence une version spéciale de FSharp.Core.

  2. Dans l'Explorateur de solutions, développez le nœud Références, puis sélectionnez le nœud FSharp.Core. Dans la fenêtre Propriétés, la valeur de la propriété FullPath doit contenir .NETPortable, pour indiquer que vous utilisez la version portable de la bibliothèque principale F#. Vous pouvez également réviser les différentes bibliothèques .NET accessibles par défaut. Ces bibliothèques utilisent toutes un sous-ensemble commun du .NET Framework défini comme sous-ensemble portable .NET. Vous pouvez supprimer les références inutiles, mais si vous ajoutez des références, votre assembly de référence doit être disponible sur toutes les plateformes que vous ciblez. La documentation pour un assembly indique généralement les plateformes sur lesquelles il est disponible.

  3. Ouvrez le menu contextuel du projet, puis sélectionnez Propriétés. Dans l'onglet Application, le framework cible a la valeur Sous-ensemble portable .NET. Pour Visual Studio 2012, ce sous-ensemble cible .NET pour les applications Windows Store, .NET Framework 4.5 et Silverlight 5. Ces paramètres sont importants, car en tant que bibliothèque portable, votre application doit s'exécuter sur le runtime disponible sur différentes plateformes. Les runtimes pour les applications Windows Store et Silverlight 5 contiennent des sous-ensembles du .NET Framework complet.

  4. Renommez le fichier de code principal, puis collez le code suivant dans la fenêtre de l'éditeur. Ce code définit la fonctionnalité d'une feuille de calcul de base.

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

Utilisation d'une bibliothèque portable dans une Application Silverlight

Procédure : créer une application Silverlight qui utilise une bibliothèque portable F#

  1. Dans la barre de menus, sélectionnez Fichier, Ajouter, puis Nouveau projet. Dans la boîte de dialogue Ajouter le nouveau projet, développez Visual C#, Silverlight, puis sélectionnez Application Silverlight. La boîte de dialogue Nouvelle application Silverlight s'ouvre.

  2. Vérifiez que la case à cocher Héberger l'application Silverlight sur un nouveau site Web est activée, que dans la liste déroulante, Projet d'application Web ASP.NET est sélectionné, puis cliquez sur OK. Deux projets sont créés : l'un dispose du contrôle Silverlight et l'autre est une application Web ASP.NET qui héberge le contrôle.

  3. Ajoutez une référence au projet Feuille de calcul. Ouvrez le menu contextuel pour le nœud Références du projet Silverlight, puis sélectionnez Ajouter une référence. Le Gestionnaire de références s'affiche. Développez le nœud Solution, puis sélectionnez le projet Feuille de calcul et cliquez sur OK.

  4. Dans cette étape, vous créez un modèle d'affichage expliquant tout ce que l'interface utilisateur doit faire, mais sans décrire la manière dont elle s'affiche. Ouvrez le menu contextuel pour le nœud du projet, sélectionnez Ajouter, puis Nouvel élément. Ajoutez un fichier de code, attribuez-lui le nom ViewModel.cs, puis copiez le code suivant dans ce fichier :

    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. Dans le projet de contrôle Silverlight, ouvrez MainPage.xaml, qui déclare la disposition de l'interface utilisateur pour la feuille de calcul principale. Dans MainPage.xaml, collez le code XAML suivant dans l'élément Grid existant.

    <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. Dans MainPage.xaml.cs, ajoutez using SilverlightFrontEnd; à la liste des directives using, puis ajoutez les méthodes suivantes à la classe 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. Dans App.xaml.cs, ajoutez les directives using suivantes :

    using SilverlightFrontEnd;
    using Portable.Samples.Spreadsheet;
    

    Collez le code suivant dans le gestionnaire d'événements Application_Startup :

                var spreadsheet = new Spreadsheet(5, 5);
                var spreadsheetViewModel = new SpreadsheetViewModel(spreadsheet);
                var main = new MainPage();
                main.DataContext = spreadsheetViewModel;
                this.RootVisual = main;
    
  8. Vous pouvez tester votre front end Silverlight en démarrant le projet Silverlight directement ou en démarrant l'application Web ASP.NET qui héberge le contrôle Silverlight. Ouvrez le menu contextuel pour le nœud de l'un de ces projets, puis sélectionnez Définir comme projet de démarrage.

Utilisation de la bibliothèque portable dans une Application Windows Store

Procédure : créer une application Windows Store qui utilise une bibliothèque portable F#

  1. Dans cette section, vous allez créer une application Windows Store qui utilise le code de feuille de calcul F# comme composant de calcul. Dans la barre de menus, cliquez sur Fichier, Ajouter, Nouveau projet. La boîte de dialogue Nouveau projet s'affiche. Sous Installés, développez Visual C#, Windows Store, puis sélectionnez le modèle Application vide. Nommez le projet NewFrontEnd, puis cliquez sur OK. Si une invite vous demande votre licence développeur pour créer des applications Windows Store, entrez vos informations d'identification. Si vous n'avez pas les informations d'identification, vous pouvez découvrir comment les générer ici.

    Le projet est créé. Notez la configuration et le contenu de ce projet. Les références par défaut incluent .NET pour les applications Windows Store, qui est le sous-ensemble du .NET Framework compatible avec les applications Windows Store, et l'assembly Windows, qui inclut les API pour Windows Runtime et l'interface utilisateur pour les applications Windows Store. Les sous-dossiers Composants et Commun ont été créés. Le sous-dossier Composant contient plusieurs icônes qui s'appliquent aux applications Windows Store, et le sous-dossier Commun contient des routines partagées que les modèles pour les applications Windows Store utilisent. Le modèle de projet par défaut a également créé App.xaml, BlankPage.xaml et leurs fichiers code-behind C# associés, App.xaml.cs et BlankPage.xaml.cs. App.xaml décrit l'ensemble de l'application, et BlankPage.xaml décrit l'aire de l'interface utilisateur définie. Pour finir, les fichiers .pfx et .appxmanifest prennent en charge les modèles de sécurité et de déploiement pour les applications Windows Store.

  2. Ajoutez une référence au projet Feuille de calcul en ouvrant le menu contextuel pour le nœud Références du projet Silverlight, puis en sélectionnant Ajouter une référence. Dans le Gestionnaire de références, développez le nœud Solution, sélectionnez le projet Feuille de calcul et cliquez sur OK.

  3. Vous aurez besoin d'une partie du code déjà utilisé dans le projet Silverlight pour prendre en charge le code pour l'interface utilisateur de l'application Windows Store. Ce code est dans ViewModels.cs. Ouvrez le menu contextuel pour le nœud du projet pour NewFrontEnd, sélectionnez Ajouter, puis Nouvel élément. Ajoutez un fichier de code C# et nommez-le ViewModels.cs. Collez le code à partir de ViewModels.cs dans le projet Silverlight, puis modifiez le bloc de directives using dans la partie supérieure de ce fichier. Supprimez System.Windows, qui est utilisé pour l'interface utilisateur Silverlight, et ajoutez Windows.UI.Xaml ainsi que Windows.Foundation.Collections, qui sont utilisés pour l'interface utilisateur de l'application Windows Store. L'interface utilisateur Windows Store et Silverlight reposent sur WPF, ce qui garantit leur compatibilité. Le bloc mis à jour de directives using doit ressembler à l'exemple suivant :

    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;
    

    En outre, dans ViewModels.cs, remplacez l'espace de noms SilverlightFrontEnd par NewFrontEnd.

    Vous pouvez réutiliser le reste du code dans ViewModels.cs, mais certains types tels que Visibilité sont maintenant les versions pour les applications Windows Store, à la place de Silverlight.

  4. Dans cette application Windows Store, le fichier de code App.xaml.cs doit avoir un code de démarrage similaire à celui qui apparaît dans le gestionnaire d'événements Application_Startup pour l'application Silverlight. Dans une application Windows Store, ce code apparaît dans le gestionnaire d'événements OnLaunched de la classe App. Ajoutez le code suivant au gestionnaire d'événements OnLaunched dans App.xaml.cs :

    var spreadsheet = new Spreadsheet(5, 5);
    var spreadsheetViewModel = new SpreadSheetViewModel(spreadsheet);
    
  5. Ajoutez une directive using pour le code de feuille de calcul.

    using Portable.Samples.Spreadsheet;
    
  6. Dans App.xaml.cs, OnLaunched contient du code qui spécifie la page à charger. Vous ajouterez une page que vous souhaitez que l'application charge lorsqu'un utilisateur la démarre. Modifiez le code dans OnLaunched pour accédez à la première page, comme l'illustre l'exemple suivant :

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

    Vous pouvez supprimer BlankPage1.xaml et son fichier code-behind puisqu'ils ne sont pas utilisés dans cet exemple.

  7. Ouvrez le menu contextuel pour le nœud du projet pour NewFrontEnd, sélectionnez Ajouter, puis Nouvel élément. Ajoutez un modèle Page Éléments et conservez le nom par défaut ItemsPage1.xaml. Cette étape ajoute ItemsPage1.xaml et son fichier code-behind, ItemsPage1.xaml.cs, au projet. ItemsPage1.xaml commence par une balise principale common:LayoutAwarePage avec plusieurs attributs, comme illustré par le code XAML suivant :

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

    L'interface utilisateur pour l'application Windows Store est identique à celle pour l'application Silverlight que vous avez créée, et le format XAML est le même dans ce cas. Par conséquent, vous pouvez réutiliser le XAML de MainPage.xaml dans le projet Silverlight pour ItemsPage1.xaml dans l'interface utilisateur de l'application Windows Store.

  8. Copiez le code dans l'élément Grid de niveau supérieur de MainPage.xaml pour le projet Silverlight et collez-le dans l'élément Grid de niveau supérieur dans ItemsPage1.xaml dans le projet pour l'interface utilisateur de l'application Windows Store. Lorsque vous collez le code, vous pouvez remplacer tout contenu existant de l'élément Grid. Remplacez la valeur de l'attribut d'arrière-plan sur l'élément Grid par « Blanc » et remplacez MouseLeftButtonDown par PointerPressed.

    Le nom de cet événement diffère dans les applications Silverlight et les applications Windows Store.

  9. Dans ItemsPage.xaml.cs, définissez la propriété DataContext en modifiant la méthode OnNavigatedTo.

    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        this.DataContext = e.Parameter;
    }
    
  10. Copiez le code de gestionnaire d'événements suivant et collez-le dans la classe ItemsPage1 : OnLostFocus, OnKeyUp, EditValue, OnPointerPressed et 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. Remplacez le projet de démarrage par le projet pour votre application Windows Store. Ouvrez le menu contextuel pour le nœud de projet NewFrontEnd, sélectionnez Définir comme projet de démarrage, puis appuyez sur la touche F5 pour exécuter le projet.

Création d'une bibliothèque portable en C# qui utilise F#

L'exemple précédent duplique du code de sorte que le code ViewModels.cs apparaît dans plusieurs projets. Dans cette section, vous créez un projet de bibliothèque portable C# pour contenir ce code. Dans certains cas, vous devez ajouter des informations au fichier de configuration de votre application lorsqu'il utilise des bibliothèques portables qui utilisent F#. Dans ce cas, une application de bureau qui cible la version pour ordinateur de bureau de .NET Framework 4.5 référence une bibliothèque portable C# qui, à son tour, référence une bibliothèque portable F#. Dans ce cas, vous devez ajouter une redirection de liaison au fichier app.config de l'application principale. Vous devez ajouter cette redirection, car une seule version de la bibliothèque FSharp.Core est chargée, mais les bibliothèques portables référencent la version .NET Portable. Les appels aux versions .NET Portable des fonctions FSharp.Core doivent être redirigés vers la version unique de FSharp.Core chargée dans une application de bureau. Les redirections de liaison ne sont nécessaires que dans l'application de bureau, car les environnements d'exécution pour les applications Windows Store et Silverlight 5 utilisent la version .NET Portable de FSharp.Core, et non la version pour ordinateur de bureau complète.

Procédure : créer une application de bureau qui référence une bibliothèque portable utilisant F#

  1. Dans la barre de menus, cliquez sur Fichier, Ajouter, Nouveau projet. Sous Installés, développez le nœud Visual C#, sélectionnez le modèle de projet Bibliothèque portable .NET, puis nommez le projet ViewModels.

  2. Vous devez définir les cibles pour que cette bibliothèque portable .NET corresponde à la bibliothèque portable F# à laquelle vous ajouterez une référence. Sinon, un message d'erreur vous informe de l'incompatibilité. Dans le menu contextuel du projet ViewModels, sélectionnez Propriétés. Dans l'onglet Bibliothèque, modifiez les cibles de cette bibliothèque portable pour qu'elles correspondent aux applications .NET Framework 4.5, Silverlight 5 et Windows Store.

  3. Dans le menu contextuel pour le nœud Références, sélectionnez Ajouter une référence. Sous Solution, activez la case à cocher en regard de Feuille de calcul.

  4. Copiez le code pour ViewModels.cs à partir de l'un des autres projets et collez-le dans le fichier de code pour le projet ViewModels.

  5. Effectuez les modifications suivantes, qui rendent le code dans ViewModels complètement indépendant de la plateforme IU :

    1. Supprimez les directives using pour System.Windows, System.Windows.Input, Windows.Foundation.Collections et Windows.UI.Xaml, le cas échéant.

    2. Remplacez l'espace de noms par ViewModels.

    3. Supprimez la propriété TooltipVisibility. Cette propriété utilise Visibilité, qui est un objet dépendant de la plateforme.

  6. Dans la barre de menus, cliquez sur Fichier, Ajouter, Nouveau projet. Sous Installés, développez le nœud Visual C#, puis sélectionnez le modèle de projet Application WPF. Nommez le nouveau projet Desktop, puis cliquez sur OK.

  7. Ouvrez le menu contextuel pour le nœud Références dans le projet Desktop, puis sélectionnez Ajouter une référence. Sous Solution, sélectionnez les projets Spreadsheet et ViewModels.

  8. Ouvrez le fichier app.config pour l'application WPF et ajoutez les lignes de code suivantes. Ce code configure les redirections de liaison appropriées qui s'appliquent lorsqu'une application de bureau qui cible .NET Framework 4.5 référence une bibliothèque portable .NET utilisant F#. Les bibliothèques portables .NET utilisent la version 2.3.5.0 de la bibliothèque FSharp.Core library et les applications de bureau .NET Framework 4.5 utilisent la version 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>
    

    Vous devez maintenant ajouter une référence à la version portable de la bibliothèque principale F#. Cette référence est requise chaque fois que vous possédez une application qui utilise une bibliothèque portable référençant une bibliothèque portable F#.

  9. Ouvrez le menu contextuel pour le nœud Références dans le projet Desktop, puis sélectionnez Ajouter une référence. Sélectionnez Parcourir, puis accédez à Reference Assemblies\Microsoft\FSharp\3.0\Runtime\.NETPortable\FSharp.Core.dll sous le dossier Program Files dans lequel Visual Studio est installé.

  10. Dans le projet Desktop, ajoutez des directives using pour ViewModels.cs et Portable.Samples.Spreadsheet à App.xaml.cs et MainWindow.xaml.cs.

    using ViewModels;
    using Portable.Samples.Spreadsheet;
    
  11. Ouvrez le fichier MainWindow.xaml, puis remplacez l'attribut title de la classe de fenêtre par Spreadsheet.

  12. Copiez le code dans l'élément Grid de MainPage.xaml dans le projet Silverlight et collez-le dans l'élément Grid de MainWindow.xaml dans le projet Desktop.

  13. Copiez le code de gestion des événements dans MainPage.xaml.cs à partir du projet Silverlight et collez ce code dans MainWindow.xaml.cs dans le projet 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. Ajoutez le code de démarrage de feuille de calcul au constructeur MainWindow dans MainWindow.xaml.cs, puis remplacez les références à MainPage par les références à MainWindow.

        public MainWindow()
        {
                var spreadsheet = new Spreadsheet(5, 5);
                var spreadsheetViewModel = new SpreadsheetViewModel(spreadsheet);
    
    
                this.DataContext = spreadsheetViewModel;
                InitializeComponent();
        }
    
  15. Ouvrez le menu contextuel pour le projet Desktop, puis sélectionnez Définir comme projet de démarrage.

  16. Appuyez sur la touche F5 pour générer l'application, puis déboguez-la.

Étapes suivantes

Vous pouvez également modifier les projets pour que l'application Windows Store et l'application Silverlight utilisent la nouvelle bibliothèque portable ViewModels.

Continuez à découvrir les applications Windows Store sur le Centre de développement Windows.

Voir aussi

Concepts

Applications Windows Store

Développement interplateforme avec la bibliothèque de classes portable

Autres ressources

Exemples et procédures pas à pas Visual F#

Silverlight