Udostępnij za pośrednictwem


Instruktaż: Tworzenie przenośne biblioteki F#

Prześledź niniejszy poradnik, aby utworzyć zestaw w F#, który będzie można wykorzystać w aplikacjach Silverlight, aplikacjach pulpitowych lub aplikacjach Windows Store, które można utworzyć za pomocą API .NET.Dzięki temu można przygotować interfejs użytkownika dla aplikacji napisanej w innym języku .NET, takim jak C# lub Virtual Basic, a także część algorytmową w F#.Można również zamieścić obsługę innych interfejsów użytkownika przeznaczonych dla innych platform.

W F# nie można używać interfejsu użytkownika Windows Store, dlatego zalecamy napisanie interfejsu użytkownika dla aplikacji Windows Store w innym języku .NET oraz zapisanie kodu F# w przenośnej bibliotece.Interfejsy Silverlight i Windows Presentation Foundation (WPF) można napisać bezpośrednio w F#, lecz warto skorzystać z dodatkowych narzędzi projektowania dostępnych dla języka C# lub Visual Basic w Visual Studio.

Wymagania wstępne

Aby utworzyć aplikację Windows Store, należy zainstalować na komputerze system Windows 8.

Aby utworzyć projekt Silverlight, należy zainstalować program Silverlight 5.

Aplikacja Spreadsheet

W tym poradniku pokażemy, jak zaprojektować prostą aplikację arkusza kalkulacyjnego, wyświetlającą siatkę komórek, w których użytkownik może wpisywać liczby i formuły.Warstwa F# przetwarza i zatwierdza wszystkie sygnały wyjściowe, a w szczególności analizuje tekst formuł i oblicza ich wyniki.Najpierw należy utworzyć algorytm F#, który zawiera w sobie kod analizowania wyrażeń wykorzystujących odwołania do komórek, liczby i operatory matematyczne.W aplikacji znajduje się kod umożliwiający śledzenie komórek, które muszą być uaktualnione, gdy użytkownik zmienia zawartość innej komórki.Następnie należy utworzyć interfejs użytkownika.

Na poniższym rysunku przedstawiono aplikację, która zostanie przygotowana za pomocą tego poradnika.

Interfejs użytkownika aplikacji Spreadsheet

Zrzut ekranu F# przenośne instruktażu końcowego aplikacji

W poradniku znajdują się następujące rozdziały.

  • How To: Create an F# Portable Library

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

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

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

Porada: tworzenie przenośnej biblioteki F#

  1. Na pasku menu, kliknij Plik, Nowy projekt.W oknie dialogowym Nowy projekt, rozwiń opcję Visual F#, następnie wybierz typ projektu F# Portable library, a następnie nazwij bibliotekę Spreadsheet.Zauważ, że projekt odwołuje się do specjalnej wersji FSharp.Core.

  2. W oknie Eksplorator rozwiązań, rozwiń węzeł Odwołania, a następnie wybierz węzeł FSharp.Core.W oknie Właściwości, wartość własności FullPath powinna pozostać niezmieniona (.NETPortable). Wskazuje to na użycie przenośnej wersji biblioteki Core F#.Możesz również przejrzeć różne biblioteki .NET dostępne domyślnie.Te biblioteki współpracują ze standardowym podzbiorem .NET Framework, który określa się jako .NET przenośny.Zbędne odwołania można usunąć, ale dodasz nowe odwołania, będą musiały być dostępne na wszystkich platformach docelowych.W dokumentacji zestawu znajdują się informacje dotyczące dostępnych platform.

  3. Otwórz menu skrótów dla projektu, a następnie wybierz Właściwości.Na karcie Aplikacja zostaje ustalona platforma docelowa — .NET Portable Subset.W przypadku Visual Studio 2012, zbiór dotyczy .NET dla aplikacji Windows Store, .NET Framework 4.5 i Silverlight 5.Te ustawienia są ważne ponieważ aplikacja będąca przenośną biblioteką musi móc uruchomić się w środowisku dostępnym na różnych platformach.Środowiska uruchomieniowe dla aplikacji Windows Store i Silverlight 5 zawiera elementy pełnego środowiska .NET Framework.

  4. Zmień nazwę głównego pliku kodu (Spreadsheet.fs), a następnie wklej następujący kod do okna edytora.Kod określa funkcjonalność podstawowego arkusza kalkulacyjnego.

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

Porada: tworzenie aplikacji Silverlight, która wykorzystuje przenośną bibliotekę F#

  1. Na pasku menu, kliknij Plik, Dodaj, a następnie Nowy projekt.W oknie dialogowym Dodaj nowy projekt, rozwiń opcję Visual C#, następnie rozwiń opcję Silverlight i wybierz Silverlight Application.Pojawi się okno dialogowe Nowa aplikacja Silverlight.

  2. Upewnij się, że pole wyboru Host the Silverlight application in a new Web site jest zaznaczone, a opcja ASP.NET Web Application Project jest zaznaczona na liście rozwijanej. Następnie naciśnij przycisk OK.Zostaną utworzone dwa projekty: jeden ze sterowaniem Silverlight a drugi jako aplikacja WWW ASP.NET, który jest hostem sterowania.

  3. Dodaj odwołanie do projektu Spreadsheet.Otwórz menu skrótów dla węzła Odwołania projektu Silverlight, a następnie wybierz opcję Dodaj odwołanie.Wyświetli się Menadżer odwołań.Rozwiń węzeł Rozwiązanie i wybierz projekt Spreadsheet, a następnie kliknij przycisk OK.

  4. Na tym etapie przygotujesz model, w którym znajdują się opisy wszystkich funkcji interfejsu użytkownika. Nie ma tu informacji o wyglądzie interfejsu.Otwórz menu skrótów dla węzła projektu, wybierz opcję Dodaj, a następnie Nowy element.Dodaj plik kodu. Nazwij go ViewModel.cs, a następnie wklej do niego poniższy kod:

    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. W opcjach kontrolnych projektu Silverlight otwórz plik MainPage.xaml, w którym deklarowany jest układ interfejsu użytkownika głównego arkusza kalkulacyjnego.W pliku MainPage.xaml, wklej poniższy kod XAML do istniejącego elementu Grid.

    <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. W pliku MainPage.xaml.cs należy dodać using SilverlightFrontEnd; do listy, korzystając z dyrektyw, a następnie dodać następujące metody do klasy 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. W pliku App.xaml.cs dodaj następujące dyrektywy:

    using SilverlightFrontEnd;
    using Portable.Samples.Spreadsheet;
    

    Dodaj następujący kod do narzędzia obsługi zdarzeń Application_Startup:

                var spreadsheet = new Spreadsheet(5, 5);
                var spreadsheetViewModel = new SpreadsheetViewModel(spreadsheet);
                var main = new MainPage();
                main.DataContext = spreadsheetViewModel;
                this.RootVisual = main;
    
  8. Front-end Silverlight można przetestować, uruchamiając bezpośrednio projekt Silverlight lub uruchamiając aplikację WWW ASP.NET, która obsługuje pliki sterowania Silverlight.Otwórz menu skrótów dla węzła jednego z projektów i wybierz opcję Set as Startup Project.

Porada: tworzenie aplikacji Windows Store, która wykorzystuje przenośną bibliotekę F#

  1. W tym rozdziale dowiesz się, jak utworzyć aplikację Windows Store wykorzystującą kod arkusza kalkulacyjnego F# jako składnika liczącego.Na pasku menu, kliknij Plik, Dodaj, Nowy projekt.Pojawi się okno dialogowe Nowy projekt.W oknie dialogowym Zainstalowane, rozwiń opcję Visual C#, następnie rozwiń opcję Windows Store i wybierz szablon Blank App.Nazwij projekt NewFrontEnd i kliknij OK.Jeśli przed utworzeniem aplikacji Windows Store pojawi się prośba o przedstawienie licencji dewelopera, należy wprowadzić odpowiednie dane.Jeśli nie masz takich danych, tutaj dowiesz się, jak je zdobyć.

    Projekt zostanie utworzony.Zapisz konfigurację i zawartość tego projektu.Domyślne odwołania zawierają .NET for Windows Store apps, który stanowi część .NET Framework, który jest zgodny z aplikacjami Windows Store oraz Windows assembly, zawierający API środowiska wykonawczego systemu Windows i interfejs użytkownika aplikacji Windows Store.Nastąpi utworzenie podfolderów Assets i Common.Podfolder Assets zawiera ikony, które są wykorzystywane przez aplikację Windows Store, natomiast podfolder Common zawiera udostępniane procedury będące szablonami dla użycia aplikacji Windows Store.Domyślny szablon projektu utworzył pliki App.xaml, BlankPage.xaml i powiązane z nimi pliki C#: App.xaml.cs i BlankPage.xaml.cs.App.xaml ogólnie opisuje aplikację, a BlankPage.xaml opisuje jeden zdefiniowany interfejs użytkownika.Natomiast pliki .pfx i .appxmanifest obsługują bezpieczeństwo i modele wdrażania aplikacji Windows Store.

  2. Dodaj odwołanie do projektu Spreadsheet, otwierając menu skrótów dla węzła Odwołania projektu Silverlight, a następnie wybierając opcję Dodaj odwołanie.W Reference Manager rozwiń węzeł Rozwiązanie i wybierz projekt Spreadsheet, a następnie kliknij przycisk OK.

  3. Do obsługi kodu interfejsu użytkownika aplikacji Windows Store trzeba wykorzystać część kodu z projektu Silverlight.Ten kod znajduje się w pliku ViewModels.cs.Otwórz menu skrótów dla węzła projektu NewFrontEnd, wybierz opcję Dodaj, a następnie Nowy element.Dodaj plik kodu C# i nazwij go ViewModels.cs.Wklej kod z pliku ViewModels.cs do projektu Silverlight, a następnie zmień blok, wykorzystując dyrektywy znajdujące się na szczycie tego pliku.Usuń plik System.Windows, który jest wykorzystywany przez interfejs Silverlight i dodaj pliki Windows.UI.Xaml i Windows.Foundation.Collection, które są wykorzystywane przez interfejs użytkownika aplikacji Windows Store.Zarówno Silverlight jak i interfejs użytkownika aplikacji Windows Store są przygotowane na podstawie WPF, więc są ze sobą kompatybilne.Zaktualizowany blok użycia dyrektyw powinien przypominać poniższy przykład:

    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;
    

    Zmień też przestrzeń nazw w pliku ViewModels.cs z SilverlightFrontEnd na NewFrontEnd.

    Pozostałą część kodu można wykorzystać w pliku ViewModels.cs, ale niektóre typy takie jak Visibility są w wersjach dla aplikacji Windows Store a nie Silverlight.

  4. W tej aplikacji Windows Store, kod App.xaml.cs musi mieć taki sam kod uruchomieniowy, jak ten, który pojawił się w narzędziu obsługi zdarzeń Application_Startup aplikacji Silverlight.W aplikacji Windows Store tek kod pojawia się w narzędziu obsługi zdarzeń OnLaunched klasy aplikacji.Dodaj następujący kod do narzędzia obsługi zdarzeń OnLaunched w App.xaml.cs:

    var spreadsheet = new Spreadsheet(5, 5);
    var spreadsheetViewModel = new SpreadSheetViewModel(spreadsheet);
    
  5. Dodaj dyrektywę użycia do kodu Spreadsheet.

    using Portable.Samples.Spreadsheet;
    
  6. W pliku App.xaml.cs OnLaunched znajduje się kod określający, która strona ma być ładowana.Musisz dodać stronę, która ma się wyświetlać po uruchomieniu aplikacji.Zmień kod w OnLaunched, aby przejść do pierwszej strony (zob. poniższy przykład):

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

    Możesz usunąć plik BlankPage1.xaml i kod odwołujący się do niego, gdyż nie jest używany w tym przykładzie.

  7. Otwórz menu skrótów dla węzła projektu NewFrontEnd, wybierz opcję Dodaj, a następnie Nowy element.Dodaj stronę z elementami, zachowaj oryginalną nazwę: ItemsPage1.xaml.W tym etapie dodawany jest do projektu plik ItemsPage1.xaml i odwołujący się do niego kod zapisany w pliku ItemsPage1.xaml.cs.Plik ItemsPage1.xaml zaczyna się głównym znacznikiem common:LayoutAwarePage z licznymi atrybutami, tak jak widać na poniższym przykładzie kodu 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">
    

    Interfejs użytkownika aplikacji Windows Store jest identyczny jak utworzony wcześniej interfejs aplikacji Silverlight. W tym przypadku taki sam jest również format XAML.Dlatego też można wykorzystać ponownie formularz XAML z pliku MainPage.xaml w projekcie Silverlight dla pliku ItemsPage1.xaml w interfejsie użytkownika dla aplikacji Windows Store.

  8. Skopiuj kod widoczny w górnym elemencie Grid w pliku MainPage.xaml dla projektu Silverlight i wklej go do górnego elementu Grid w pliku ItemsPage1.xaml projektu interfejsu użytkownika aplikacji Windows Store.Wklejając kod, możesz nadpisać istniejącą zawartość elementu Grid.Zmień atrybut Background w elemencie Grid na „White” i zamień MouseLeftButtonDown na PointerPressed.

    Nazwa tego zdarzenia różni się w aplikacji Silverlight i aplikacjach Windows Store.

  9. W pliku ItemsPage.xaml.cs ustaw właściwość DataContext, zmieniając metodę OnNavigatedTo.

    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        this.DataContext = e.Parameter;
    }
    
  10. Skopiuj następujący kod obsługi zdarzenia i wklej go do klasy ItemsPage1: OnLostFocus, OnKeyUp, EditValue, OnPointerPressed i 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. Zmień projekt uruchomieniowy na swój projekt aplikacji Windows Store.Otwórz menu skrótów dla węzła projektu NewFrontEnd i wybierz opcję Set as Startup Project, a następnie naciśnij klawisz F5, aby uruchomić projekt.

Tworzenie przenośnej biblioteki wykorzystującej język F# w C#

W powyższym przykładzie kopiowany jest kod z pliku ViewModels.cs w taki sposób, że pojawia się w wielu projektach.W tej części utworzysz projekt przenośnej biblioteki C#, w którym ten kod będzie zawarty.W niektórych przypadkach trzeba dodać informację o aplikacji do pliku konfiguracyjnego, gdy posługuje się ona przenośnymi bibliotekami wykorzystującymi język F#.W takim przypadku aplikacja dla pulpitu wykorzystująca pulpitową wersję .NET Framework 4.5 odwołuje się do przenośnej biblioteki C#, która odwołuje się do biblioteki F#.W takim przypadku należy dodać wiązanie przekierowujące do pliku app.config głównej aplikacji.Zaleca się dodanie tego przekierowania ponieważ tylko jedna wersja biblioteki FSharp.Core jest ładowana, ale przenośne biblioteki odwołują się do wersji przenośnej .NET Portable.Wszystkie wywołania .NET Portable funkcji FSharp.Core muszą być przekierowywane do jednej wersji biblioteki FSharp.Core, która jest ładowana razem z aplikacją.Wiązania przekierowujące są niezbędne tylko w aplikacjach pulpitowych, ponieważ środowiska wykonawcze Silverlight 5 i aplikacji Windows Store wykorzystują przenośną wersję biblioteki FSharp.Core .NET Portable, a nie pełną wersję.

Porada: tworzenie aplikacji pulpitowych, która odwołuje się do przenośnej biblioteki wykorzystującej F#

  1. Na pasku menu, kliknij Plik, Dodaj, Nowy projekt.W menu Zainstalowane rozwiń węzeł Visual C#, wybierz szablon projektu .NET Portable Library i nazwij go ViewModels.

  2. Zaleca się dopasowanie elementów docelowych tej biblioteki .NET Portable do biblioteki przenośnej F# Portable Library, do której dodasz odwołanie.Inaczej pojawi się komunikat o błędzie wynikającym z niedopasowania.Z menu skrótów dla projektu ViewModels, wybierz Właściwości.Na zakładce Library zmień pliki docelowe tej biblioteki przenośne w taki sposób, aby dopasować je do bibliotek .NET Framework 4.5, Silverlight 5 i aplikacji Windows Store.

  3. Z menu skrótów dla węzła Odwołania, wybierz opcję Dodaj odwołanie.W opcji Rozwiązanie, zaznacz pole wyboru znajdujące się obok projektu Spreadsheet.

  4. Skopiuj kod dla pliku ViewModels.cs z innych projektów i wklej go do pliku kodu projektu ViewModels.

  5. Wprowadź poniższe zmiany, które sprawią, że kod ViewModels stanie się niezależny od platformy interfejsu użytkownika:

    1. Usuń dyrektywy użytkowania dla plików System.Windows, System.Windows.Input, Windows.Foundation.Collections i Windows.UI.Xaml, jeśli są obecne.

    2. Zmień przestrzeń nazw na ViewModels.

    3. Użyj właściwość TooltipVisibility.Ta właściwość wykorzystuje obiekt Visibility, który jest zależny od platformy.

  6. Na pasku menu, kliknij Plik, Dodaj, Nowy projekt.W oknie dialogowym Zainstalowane, rozwiń węzeł Visual C#, następnie wybierz szablon projektu WPF Application.Nadaj nowemu projektowi nazwę Desktop i kliknij przycisk OK.

  7. Otwórz menu skrótów dla węzła Odwołania projektu Desktop, a następnie wybierz opcję Dodaj odwołanie.W opcji Rozwiązanie, wybierz projekty Spreadsheet i ViewModels.

  8. Otwórz plik app.config dla aplikacji WPF, a następnie dodaj poniższe wiersze kodu.Kod konfiguruje odpowiednie przekierowania powiązań, które są wykorzystywane, gdy aplikacja odwołuje się do biblioteki .NET Framework 4.5 za pośrednictwem biblioteki .NET Portable Library wykorzystującej język F#.Biblioteki przenośne .NET Portable wykorzystują bibliotekę FSharp.Core w wersji 2.3.5.0, a aplikacje pulpitowe wykorzystują bibliotekę .NET Framework 4.5 w wersji 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>
    

    Teraz trzeba dodać odwołanie do przenośnej wersji biblioteki F# Core.To odwołanie jest niezbędne w przypadku aplikacji, która wykorzystuje przenośną bibliotekę, która odwołuje się do biblioteki F#.

  9. Otwórz menu skrótów dla węzła Odwołania projektu Desktop, a następnie wybierz opcję Dodaj odwołanie.Kliknij Przeglądaj, a następnie przejdź do lokalizacji Reference Assemblies\Microsoft\FSharp\3.0\Runtime\.NETPortable\FSharp.Core.dll w folderze Program Files, gdzie zainstalowano Visual Studio.

  10. W projekcie Desktop, dodaj dyrektywy dla plików ViewModels.cs i Portable.Samples.Spreadsheet do plików App.xaml.cs i MainWindow.xaml.cs.

    using ViewModels;
    using Portable.Samples.Spreadsheet;
    
  11. Otwórz plik MainWindow.xaml, a następnie zmień tytuł atrybutu klasy Window na Spreadsheet.

  12. Skopiuj kod widoczny w górnym elemencie Grid w pliku MainPage.xaml w projekcie Silverlight i wklej go do górnego elementu Grid w pliku MainWindow.xaml projektu Desktop.

  13. Skopiuj kod narzędzia obsługi zdarzeń z pliku MainPage.cs w z projektu Silverlight i wklej go do pliku MainWindow.xaml.cs projektu 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. Dodaj kod uruchomieniowy arkusza kalkulacyjnego do konstruktora MainWindow w pliku MainWindow.xaml.cs i zamień odwołania do MainPage na odwołania do MainWindow.

        public MainWindow()
        {
                var spreadsheet = new Spreadsheet(5, 5);
                var spreadsheetViewModel = new SpreadsheetViewModel(spreadsheet);
    
    
                this.DataContext = spreadsheetViewModel;
                InitializeComponent();
        }
    
  15. Otwórz menu skrótów dla projektu Desktop i wybierz opcję Set as Startup Project.

  16. Naciśnij klawisz F5, aby skompilować aplikację, a następnie rozpocznij debugowanie.

Następne kroki

Alternatywnie można zmodyfikować projekty aplikacji Windows Store i Silverlight, aby wykorzystywały bibliotekę przenośną ViewModels.

Kontynuuj, aby dowiedzieć się więcej o aplikacjach Windows Store w Centrum deweloperów systemu Windows.

Zobacz też

Koncepcje

Aplikacje sklepu Windows Store

Tworzenie aplikacji wieloplatformowych za pomocą oprogramowania .NET Framework

Inne zasoby

Wizualne F# próbek oraz instruktaże

Silverlight