다음을 통해 공유


연습: 이식 가능한 F# 라이브러리 만들기

이 연습을 수행함으로써 Silverlight 응용 프로그램, 전통적인 데스크톱 응용 프로그램 또는 .NET API를 사용하여 만드는 Windows 스토어 앱과 함께 사용할 수 있는 F#에서 어셈블리를 만들 수 있습니다.이런 식으로 다른 C# 또는 Visual Basic 같은 .NET 언어로 응용 프로그램의 UI 부분을 작성하고 F#으로 알고리즘 부분을 작성할 수 있습니다.서로 다른 플랫폼을 대상으로 하는 다양한 사용자 인터페이스를 지원할 수도 있습니다.

F#에서 Windows 스토어 UI를 직접 사용할 수 없으므로 다른 .NET 언어에서 Windows 스토어 앱의 UI를 작성하여 이식 가능한 라이브러리에 F# 코드를 작성하는 것이 좋습니다.Silverlight 및 WPF(Windows Presentation Foundation) UI를 F#에 직접 작성할 수 있지만 Visual Studio에서 C# 또는 Visual Basic 코드를 작성할 때 사용할 수 있는 추가적인 디자인 도구를 활용하려 할 수도 있습니다.

사전 요구 사항

Windows 스토어 앱을 만들려면 개발 컴퓨터에 Windows 8이 설치되어 있어야 합니다.

Silverlight 프로젝트를 만들려면 개발 컴퓨터에 Silverlight 5가 설치되어 있어야 합니다.

스프레드시트 응용 프로그램

이 연습에서는 사용자에게 표시되고 해당 셀에 숫자와 공식을 입력할 수 있는 간단한 스프레드시트를 개발합니다.F# 레이어는 모든 입력을 처리하고 유효성을 검사하며 특히 공식 텍스트를 구문 분석하고 공식 결과를 계산합니다.첫째, 셀 참조, 번호 및 수학 연산자를 포함하는 식을 구문 분석하는 코드를 포함하는 F# 알고리즘 코드를 만듭니다.이 응용 프로그램은 사용자가 다른 셀의 콘텐츠를 업데이트할 때 업데이트해야 하는 셀을 추적하는 코드도 포함합니다.다음 단계로, 사용자 인터페이스를 만듭니다.

다음 예제에서는 이 연습에서 만들어지는 응용 프로그램을 보여줍니다.

스프레드시트 응용 프로그램 사용자 인터페이스

최종 앱의 F# 이식 가능 연습 스크린 샷

이 연습에는 다음 단원이 포함되어 있습니다.

  • How To: Create an F# Portable Library

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

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

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

방법: F# 이식 가능한 라이브러리 만들기

  1. 메뉴 모음에서 파일, 새 프로젝트를 선택합니다.새 프로젝트 대화 상자에서 **Visual F#**를 확장하고 F# 이식 가능한 라이브러리 프로젝트 형식을 선택한 다음 라이브러리 Spreadsheet의 이름을 지정합니다.프로젝트에서 특수 버전의 FSharp.Core를 참조합니다.

  2. 솔루션 탐색기에서 참조 노드를 확장하고 FSharp.Core 노드를 선택합니다.속성 창에서 FullPath 속성 값은 .NETPortable을 포함해야 하며, 이는 코어 F# 라이브러리의 이식 가능한 버전을 사용 중임을 나타냅니다.기본적으로 액세스할 수 있는 다양한 .NET 라이브러리를 검토할 수도 있습니다.이러한 모든 라이브러리는 .NET 이식 가능한 작업으로 정의된 .NET Framework의 공통 하위 집합에 작동합니다.필요하지 않은 참조를 제거할 수 있지만 참조를 추가하는 경우 참조 어셈블리를 대상으로 하는 모든 플랫폼에서 사용할 수 있어야 합니다.어셈블리에 대한 설명서는 대개 플랫폼에서 사용할 수 있음을 나타냅니다.

  3. 프로젝트에 대한 바로 가기 메뉴를 열고 속성을 선택합니다.응용 프로그램 탭에서 대상 프레임워크를 .NET 이식 가능한 하위 집합으로 변경합니다.Visual Studio 2012의 경우 이 하위 집합은 Windows 스토어 앱용 .NET, .NET Framework 4.5 및 Silverlight 5를 대상으로 합니다.이러한 설정은 이식 가능한 라이브러리처럼 응용 프로그램이 다양한 플랫폼에서 사용할 수 있는 런타임에 대해 실행해야 하기 때문에 중요합니다.Windows 스토어 앱 및 Silverlight 5의 런타임에는 전체 .NET Framework의 하위 집합이 포함됩니다.

  4. 주 코드 파일인 Spreadsheet.fs의 이름을 바꾼 다음 편집기 창에 다음 코드를 붙여 넣습니다.이 코드는 기본 스프레드시트의 기능을 정의합니다.

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

방법: F# 이식 가능한 라이브러리를 사용하는 Silverlight 앱 만들기

  1. 메뉴 모음에서 파일, 추가를 선택한 다음 새 프로젝트를 선택합니다.새 프로젝트 추가 대화 상자에서 **Visual C#**를 확장하고 Silverlight를 확장한 다음 Silverlight 응용 프로그램을 선택합니다.새 Silverlight 응용 프로그램 대화 상자가 나타납니다.

  2. 새 웹 사이트에서 Silverlight 응용 프로그램 호스트 확인란이 선택되었는지 확인하고 드롭다운에서 ASP.NET 웹 응용 프로그램 프로젝트가 선택되었는지 확인한 다음 확인 단추를 선택합니다.두 프로젝트가 만들어집니다: 한 프로젝트는 Silverlight 컨트롤이 있으며 다른 프로젝트는 컨트롤을 호스팅하는 ASP.NET 웹 앱입니다.

  3. 스프레드시트 프로젝트에 참조를 추가합니다.Silverlight 프로젝트의 참조 노드에 대한 바로 가기 메뉴를 열고 참조 추가를 선택합니다.참조 관리자가 나타납니다.솔루션 노드를 확장하고 스프레드시트 프로젝트를 선택한 후 확인 단추를 선택합니다.

  4. 이 단계에서는 UI가 나타나는 방법에 대한 설명 없이 UI가 수행해야 하는 모든 것을 설명하는 뷰 모델을 만듭니다.프로젝트 노드에 대한 바로 가기 메뉴를 열고 추가를 선택한 다음 새 항목을 선택합니다.코드 파일을 추가하고, ViewModel.cs라는 이름을 지정한 후 다음 코드를 붙여넣습니다.

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Input;
    using Portable.Samples.Spreadsheet;
    
    namespace SilverlightFrontEnd
    {
        public class SpreadsheetViewModel
        {
            private Spreadsheet spreadsheet;
            private Dictionary<string, CellViewModel> cells = new Dictionary<string, CellViewModel>();
    
            public List<RowViewModel> Rows { get; private set; }
            public List<string> Headers { get; private set; }
    
    
            public string SourceCode
            {
                get
                {
                    return @"
    type Spreadsheet(height : int, width : int) = 
    
        do 
            if height <= 0 then failwith ""Height should be greater than zero""
            if width <= 0 || width > 26 then failwith ""Width should be greater than zero and lesser than 26""
    
        let rowNames = [| for i = 0 to height - 1 do yield string (i + 1)|]
        let colNames = [| for i = 0 to (width - 1) do yield string (char (int 'A' + i)) |]
    
        let isValidReference (s : string) = 
            if s.Length < 2 then false
            else
            let c = s.[0..0]
            let r = s.[1..]
            (Array.exists ((=)c) colNames) && (Array.exists ((=)r) rowNames)
    
        let dependencies = Dependencies.Graph()
        let formulas = Dictionary<_, Expression>()
    
        let values = Dictionary()
        let rawValues = Dictionary()
    
        let setError cell text = 
            values.[cell] <- EvalResult.E text
    
        let getValue reference = 
            match values.TryGetValue reference with
            | true, v -> v
            | _ -> EvalResult.S 0.0
    
        let deleteValue reference = 
            values.Remove(reference)
            |> ignore
    
        let deleteFormula cell = 
            match formulas.TryGetValue cell with
            | true, expr ->
                dependencies.Delete(cell, expr.GetReferences())
                formulas.Remove(cell) 
                |> ignore
            | _ -> ()
    ";
                }
            }
    
            public SpreadsheetViewModel(Spreadsheet spreadsheet)
            {
                this.spreadsheet = spreadsheet;
                Rows = new List<RowViewModel>();
                foreach (var rowRef in spreadsheet.GetRowReferences())
                {
                    var rowvm = new RowViewModel { Index = rowRef.Name, Cells = new List<CellViewModel>() };
    
                    foreach (var reference in rowRef.Cells)
                    {
                        var cell = new CellViewModel(this, reference);
                        cells.Add(reference, cell);
                        rowvm.Cells.Add(cell);
                    }
                    Rows.Add(rowvm);
    
                }
                Headers = new[] { "  " }.Concat(spreadsheet.Headers).ToList();
            }
    
            public void SetCellValue(string reference, string newText)
            {
                var affectedCells = spreadsheet.SetValue(reference, newText);
                foreach (var cell in affectedCells)
                {
                    var cellVm = cells[cell.Reference];
                    cellVm.RawValue = cell.RawValue;
    
                    if (cell.HasError)
                    {
                        cellVm.Value = "#ERROR";
                        cellVm.Tooltip = cell.Value; // will contain error
                    }
                    else
                    {
                        cellVm.Value = cell.Value;
                        cellVm.Tooltip = cell.RawValue;
                    }
                }
            }
        }
    
        public class RowViewModel
        {
            public string Index { get; set; }
            public List<CellViewModel> Cells { get; set; }
        }
    
        public class CellViewModel : INotifyPropertyChanged
        {
            private SpreadsheetViewModel spreadsheet;
    
            private string rawValue;
            private string value;
            private string reference;
            private string tooltip;
    
            public CellViewModel(SpreadsheetViewModel spreadsheet, string reference)
            {
                this.spreadsheet = spreadsheet;
                this.reference = reference;
            }
    
            public string RawValue
            {
                get
                {
                    return rawValue;
                }
                set
                {
                    var changed = rawValue != value;
                    rawValue = value;
                    if (changed) RaisePropertyChanged("RawValue");
                }
            }
            public string Value
            {
                get
                {
                    return value;
                }
                set
                {
                    var changed = this.value != value;
                    this.value = value;
                    if (changed) RaisePropertyChanged("Value");
                }
            }
            public string Tooltip
            {
                get
                {
                    return tooltip;
                }
                set
                {
                    var changed = this.tooltip != value;
                    this.tooltip = value;
                    if (changed)
                    {
                        RaisePropertyChanged("Tooltip");
                        RaisePropertyChanged("TooltipVisibility");
                    }
                }
            }
    
            public Visibility TooltipVisibility
            {
                get { return string.IsNullOrEmpty(tooltip) ? Visibility.Collapsed : Visibility.Visible; }
            }
    
            public event PropertyChangedEventHandler PropertyChanged = delegate { };
    
            public void SetCellValue(string newValue)
            {
                spreadsheet.SetCellValue(reference, newValue);
            }
    
            private void RaisePropertyChanged(string name)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }
    }
    
  5. Silverlight 컨트롤 프로젝트에서 주 스프레드시트에 대한 UI 레이아웃을 선언하는 MainPage.xaml을 엽니다.MainPage.xaml에서 기존 Grid 요소에 다음 XAML 코드를 붙여 넣습니다.

    <TextBlock Text="{Binding SourceCode}" FontSize="20" FontFamily="Consolas" Foreground="LightGray"/>
        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
          <StackPanel.Resources>
            <Style x:Key="CellBorder" TargetType="Border">
              <Setter Property="BorderThickness" Value="0.5"/>
              <Setter Property="BorderBrush" Value="LightGray"/>
            </Style>
            <Style x:Key="CaptionBorder" TargetType="Border" BasedOn="{StaticResource CellBorder}">
              <Setter Property="Background" Value="LightBlue"/>
            </Style>
            <Style x:Key="TextContainer" TargetType="TextBlock">
              <Setter Property="FontSize" Value="26"/>
              <Setter Property="FontFamily" Value="Segoe UI"/>
              <Setter Property="Width" Value="200"/>
              <Setter Property="Height" Value="60"/>
            </Style>
    
            <Style x:Key="CaptionText" TargetType="TextBlock" BasedOn="{StaticResource TextContainer}">
              <Setter Property="TextAlignment" Value="Center"/>
              <Setter Property="Foreground" Value="DimGray"/>
            </Style>
            <Style x:Key="ValueEditor" TargetType="TextBox">
              <Setter Property="Width" Value="200"/>
              <Setter Property="Height" Value="60"/>
              <Setter Property="FontSize" Value="26"/>
              <Setter Property="FontFamily" Value="Segoe UI"/>
    
            </Style>
            <Style x:Key="ValueText" TargetType="TextBlock" BasedOn="{StaticResource TextContainer}">
              <Setter Property="TextAlignment" Value="Center"/>
              <Setter Property="VerticalAlignment" Value="Center"/>
              <Setter Property="Foreground" Value="Black"/>
            </Style>
    
          </StackPanel.Resources>
          <Border Style="{StaticResource CellBorder}">
            <StackPanel>
    
              <ItemsControl ItemsSource="{Binding Headers}">
                <ItemsControl.ItemsPanel>
                  <ItemsPanelTemplate>
                    <VirtualizingStackPanel Orientation="Horizontal" />
                  </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                <ItemsControl.ItemTemplate>
                  <DataTemplate>
                    <Border Style="{StaticResource CaptionBorder}">
                      <TextBlock Text="{Binding}" Style="{StaticResource CaptionText}"/>
                    </Border>
                  </DataTemplate>
                </ItemsControl.ItemTemplate>
              </ItemsControl>
    
              <ItemsControl ItemsSource="{Binding Rows}">
                <ItemsControl.ItemTemplate>
                  <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                      <Border Style="{StaticResource CaptionBorder}">
                        <TextBlock Text="{Binding Index}" Style="{StaticResource CaptionText}"/>
                      </Border>
                      <ItemsControl ItemsSource="{Binding Cells}">
                        <ItemsControl.ItemsPanel>
                          <ItemsPanelTemplate>
                            <VirtualizingStackPanel  Orientation="Horizontal"/>
                          </ItemsPanelTemplate>
                        </ItemsControl.ItemsPanel>
                        <ItemsControl.ItemTemplate>
                          <DataTemplate>
                            <Border Style="{StaticResource CellBorder}">
                              <Grid>
                                <TextBox
                                  Name="editor"
                                  Tag="{Binding ElementName=textContainer}"
                                  Visibility="Collapsed"
                                  LostFocus="OnLostFocus"
                                  KeyUp="OnKeyUp"
                                  Text ="{Binding RawValue}"
                                  Style="{StaticResource ValueEditor}"/>
                                <TextBlock
                                  Name="textContainer"
                                  Tag="{Binding ElementName=editor}"
                                  Visibility="Visible"
                                  Text="{Binding Value}"
                                  Style="{StaticResource ValueText}"
                                  MouseLeftButtonDown="OnMouseLeftButtonDown"
                                  ToolTipService.Placement="Mouse">
                                  <ToolTipService.ToolTip>
                                    <ToolTip Visibility="{Binding TooltipVisibility}">
                                      <TextBlock Text="{Binding Tooltip}" Style="{StaticResource TextContainer}" Visibility="{Binding TooltipVisibility}"/>
                                    </ToolTip>
                                  </ToolTipService.ToolTip>
                                </TextBlock>
                              </Grid>
                            </Border>
                          </DataTemplate>
                        </ItemsControl.ItemTemplate>
                      </ItemsControl>
                    </StackPanel>
                  </DataTemplate>
                </ItemsControl.ItemTemplate>
              </ItemsControl>
    
            </StackPanel>
          </Border>
        </StackPanel>
    
  6. MainPage.xaml.cs에서 사용 중인 지시어 목록에 using SilverlightFrontEnd;를 추가한 후에 다음 메서드를 SilverlightApplication1 클래스에 추가합니다.

            void OnLostFocus(object sender, RoutedEventArgs e)
            {
                var editor = (TextBox)e.OriginalSource;
                var text = editor.Text;
    
                HideEditor(e);
    
                EditValue(editor.DataContext, text);
            }
    
            void OnKeyUp(object sender, KeyEventArgs e)
            {
                if (e.Key == Key.Escape)
                {
                    HideEditor(e);
                    e.Handled = true;
                    return;
                }
                else if (e.Key == Key.Enter)
                {
                    var editor = (TextBox)e.OriginalSource;
                    var text = editor.Text;
    
                    HideEditor(e);
    
                    EditValue(editor.DataContext, text);
                    e.Handled = true;
                }
            }
    
            private void EditValue(object dataContext, string newText)
            {
                var cvm = (CellViewModel)dataContext;
                cvm.SetCellValue(newText);
            }
    
            private void OnMouseLeftButtonDown(object sender, RoutedEventArgs e)
            {
                var textBlock = (TextBlock)e.OriginalSource;
                var editor = (TextBox)textBlock.Tag;
                textBlock.Visibility = Visibility.Collapsed;
                editor.Visibility = Visibility.Visible;
                editor.Focus();
            }
    
            private void HideEditor(RoutedEventArgs e)
            {
                var editor = (TextBox)e.OriginalSource;
                var textBlock = (TextBlock)editor.Tag;
                editor.Visibility = Visibility.Collapsed;
                textBlock.Visibility = Visibility.Visible;
            }
    
  7. App.xaml.cs에서 해당 파일에 지시문을 사용하여 다음을 추가합니다.

    using SilverlightFrontEnd;
    using Portable.Samples.Spreadsheet;
    

    Application_Startup 이벤트 처리기에 다음 코드를 붙여넣습니다.

                var spreadsheet = new Spreadsheet(5, 5);
                var spreadsheetViewModel = new SpreadsheetViewModel(spreadsheet);
                var main = new MainPage();
                main.DataContext = spreadsheetViewModel;
                this.RootVisual = main;
    
  8. Silverlight 프로젝트를 직접 시작하거나 Silverlight 컨트롤을 호스팅하는 ASP.NET 웹 응용 프로그램을 시작하여 Silverlight 프런트 엔드를 테스트할 수 있습니다.이러한 프로젝트 노드의 바로 가기 메뉴를 열고 시작 프로젝트로 설정을 선택합니다.

방법: F# 이식 가능한 라이브러리를 사용하는 Windows 스토어 앱 만들기

  1. 이 단원에서는 F# 스프레드시트 코드를 해당 계산 구성 요소로 사용하는 Windows 스토어 앱을 만듭니다.메뉴 모음에서 파일, 추가, 새 프로젝트를 선택합니다.새 프로젝트 대화 상자가 나타납니다.설치됨 아래에서 Visual **Visual C#**을 확장하고 Windows 스토어를 확장한 다음 새 응용 프로그램 템플릿을 선택합니다.프로젝트 NewFrontEnd의 이름을 지정한 다음 확인 단추를 선택합니다.Windows 스토어 앱을 만들기 위해 개발자 라이선스에 대한 메시지가 나타나면 자격 증명을 입력합니다.자격 증명이 없으면 여기에서 설정 방법을 찾아볼 수 있습니다.

    프로젝트가 만들어집니다.이 프로젝트의 구성과 내용에 유의하십시오.기본 참조에는 Windows 스토어 앱과 호환되는 .NET Framework의 하위 집합인 Windows 스토어 앱용 .NET 및 Windows Runtime용 API와 Windows 스토어 앱용 UI를 포함하는 Windows 어셈블리가 포함됩니다.자산 및 공용 하위 폴더를 만들었습니다.자산 하위 폴더에는 Windows 스토어 앱에 적용되는 여러 가지 아이콘이 포함되어 있으며 공용 하위 폴더에는 Windows 스토어 앱용 템플릿이 사용하는 공유 루틴이 포함되어 있습니다.기본 프로젝트 템플릿에서는 App.xaml, BlankPage.xaml 및 관련 C# 코드 숨김 파일, App.xaml.cs 및 BlankPage.xaml.cs도 만들었습니다.App.xaml은 전체 응용 프로그램을 설명하고 BlankPage.xaml은 하나의 정의된 UI 화면을 설명합니다.마지막으로, .pfx files and .appxmanifest 파일은 Windows 스토어 앱에 대한 보안 및 배포 모델을 지원합니다.

  2. Silverlight 프로젝트의 참조 노드에 대한 바로 가기 메뉴를 열고 참조 추가를 선택하여 스프레드시트에 참조를 추가합니다.참조 관리자에서 솔루션 노드를 확장하고 스프레드시트 프로젝트를 선택한 후 확인 단추를 선택합니다.

  3. Windows 스토어 앱의 UI에 대한 코드를 지원하도록 Silverlight 프로젝트에서 이미 사용된 코드 중 일부가 필요합니다.이 코드는 ViewModels.cs에 해당합니다.NewFrontEnd의 프로젝트 노드에 대한 바로 가기 메뉴를 열고 추가를 선택한 다음 새 항목을 선택합니다.C# 코드 파일을 추가하고 ViewModels.cs라는 이름을 지정합니다.Silverlight 프로젝트에서 ViewModels.cs의 코드를 붙여 넣은 다음 이 파일 상단에서 지시문을 사용하여 블록을 변경합니다.Silverlight UI에 사용되는 System.Windows를 제거하고 Windows 스토어 앱의 UI에 사용되는 Windows.UI.Xaml 및 Windows.Foundation.Collections를 추가합니다.Silverlight와 Windows 스토어 UI는 모두 WPF를 기반으로 하므로 서로 호환됩니다.사용 중인 지시문의 업데이트된 블록은 다음 예제와 유사합니다.

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

    또한, SilverlightFrontEnd에서 NewFrontEnd로 ViewModels.cs의 네임스페이스를 변경합니다.

    ViewModels.cs 코드의 나머지 부분을 다시 사용할 수 있지만 표시 유형과 같은 일부 형식은 현재 Silverlight 대신 Windows 스토어 앱의 버전입니다.

  4. 이 Windows 스토어 앱에서 App.xaml.cs 코드 파일은 Silverlight 응용 프로그램에서 Application_Startup 이벤트 처리기에 나타난 것과 비슷한 시작 코드가 있어야 합니다.Windows 스토어 앱에서 이 코드는 App 클래스의 OnLaunched 이벤트 처리기에 나타납니다.App.xaml.cs에서 다음 코드를 OnLaunched 이벤트 핸들러에 추가합니다.

    var spreadsheet = new Spreadsheet(5, 5);
    var spreadsheetViewModel = new SpreadSheetViewModel(spreadsheet);
    
  5. 스프레드시트 코드에 대한 지시문을 추가합니다.

    using Portable.Samples.Spreadsheet;
    
  6. App.xaml.cs에서 OnLaunched에는 로드할 페이지를 지정하는 코드가 포함되어 있습니다.사용자가 앱을 시작하면 이 앱을 로드할 페이지를 추가합니다.OnLaunched의 코드를 변경하여 첫 페이지로 이동합니다(아래 예제 그림 참조).

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

    이 예제에서는 사용되지 않으므로 BlankPage1.xaml 및 해당 코드 숨김 파일을 삭제할 수 있습니다.

  7. NewFrontEnd의 프로젝트 노드에 대한 바로 가기 메뉴를 열고 추가를 선택한 다음 새 항목을 선택합니다.항목 페이지를 추가하고 기본 이름인 ItemsPage1.xaml을 유지합니다.이 단계에서는 ItemsPage1.xaml 및 코드 숨김 파일, ItemsPage1.xaml.cs를 모두 프로젝트에 추가합니다.ItemsPage1.xaml은 많은 특성이 있는 common:LayoutAwarePage의 기본 태그로 시작합니다. 다음의 XAML 코드로 표시됩니다.

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

    Windows 스토어 앱의 UI는 사용자가 만든 Silverlight 응용 프로그램의 UI와 동일하며 XAML 형식은 이 경우 동일합니다.따라서 Windows 스토어 앱에 대한 UI에서 ItemsPage1.xaml용 Silverlight 프로젝트에서 MainPage.xaml용 XAML을 다시 사용할 수 있습니다.

  8. Silverlight 프로젝트용 MainPage.xaml의 최상위 수준 표 요소 내의 코드를 복사하고 Windows 스토어 앱의 UI에 대해 프로젝트에 ItemsPage1.xaml의 최상위 수준 Grid 요소에 붙여넣습니다.코드를 붙여넣으면 Grid 요소의 모든 기존 내용을 덮어쓸 수 있습니다.표 요소에서 배경 특성을 "흰색"으로 변경하고 MouseLeftButtonDownPointerPressed로 바꿉니다.

    이 이벤트의 이름은 Silverlight 응용 프로그램 및 Windows 스토어 앱에서 서로 다릅니다.

  9. ItemsPage.xaml.cs에서 OnNavigatedTo 메서드를 변경하여 DataContext 속성을 설정합니다.

    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        this.DataContext = e.Parameter;
    }
    
  10. 다음 이벤트 처리기 코드를 복사하고 ItemsPage1 클래스 OnLostFocus, OnKeyUp, EditValue, OnPointerPressedHideEditor에 붙여 넣습니다.

    void OnLostFocus(object sender, RoutedEventArgs e)
            {
                var editor = (TextBox)e.OriginalSource;
                var text = editor.Text;
    
                HideEditor(e);
    
                EditValue(editor.DataContext, text);
            }
    
            void OnKeyUp(object sender, KeyEventArgs e)
            {
                if (e.Key == Windows.System.VirtualKey.Escape)
                {
                    HideEditor(e);
                    e.Handled = true;
                    return;
                }
                else if (e.Key == Windows.System.VirtualKey.Enter)
                {
                    var editor = (TextBox)e.OriginalSource;
                    var text = editor.Text;
    
                    HideEditor(e);
    
                    EditValue(editor.DataContext, text);
                    e.Handled = true;
                }            
            }
    
            private void EditValue(object dataContext, string newText)
            {
                var cvm = (CellViewModel)dataContext;
                cvm.SetCellValue(newText);
            }
    
            private void OnPointerPressed(object sender, RoutedEventArgs e)
            {
                var textBlock = (TextBlock)e.OriginalSource;
                var editor = (TextBox)textBlock.Tag;
                textBlock.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
                editor.Visibility = Windows.UI.Xaml.Visibility.Visible;
    
                editor.Focus(FocusState.Programmatic);
            }
    
            private void HideEditor(RoutedEventArgs e)
            {
                var editor = (TextBox)e.OriginalSource;
                var textBlock = (TextBlock)editor.Tag;
                editor.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
                textBlock.Visibility = Windows.UI.Xaml.Visibility.Visible;
            }
    
  11. 시작 프로젝트를 Windows 스토어 앱용 프로젝트로 변경합니다.NewFrontEnd 프로젝트 노드의 바로 가기 메뉴를 열고 시작 프로젝트로 설정을 선택한 다음 F5 키를 선택하여 프로젝트를 실행합니다.

F#을 사용하여 이식 가능한 라이브러리 만들기

이전 샘플에서는 ViewModels.cs 코드가 여러 프로젝트에서 나타나는 코드와 중복됩니다.이 단원에서는 이 코드를 포함하는 C# 이식 가능한 라이브러리 프로젝트를 만듭니다.경우에 따라 F#을 사용하는 이식 가능한 라이브러리를 사용할 때 응용 프로그램의 구성 파일에 정보를 추가해야 합니다.이 경우 .NET Framework 4.5의 데스크톱 버전을 대상으로 하는 데스크톱 응용 프로그램은 F# 이식 가능한 라이브러리를 참조하는 C# 이식 가능한 라이브러리를 참조합니다.이런 경우 바인딩 리디렉션을 주 응용 프로그램의 app.config 파일에 추가해야 합니다.FSharp.Core 라이브러리의 버전이 하나만 로드되므로 이 리디렉션을 추가해야 하지만 휴대용 라이브러리에서 .NET 이식 가능 버전을 참조합니다.FSharp.Core 함수의 .NET 이식 가능 버전에 대한 호출은 데스크톱 응용 프로그램에 로드된 FSharp.Core의 단일 버전으로 리디렉션되어야 합니다.바인딩 리디렉션은 Silverlight 5 및 Windows 스토어 앱이 전체 데스크톱 버전이 아닌 FSharp.Core의 .NET Portable 버전을 사용하기 때문에 데스크톱 응용 프로그램에서만 필요합니다.

방법: F#을 사용하는 이식 가능한 라이브러리를 참조하는 데스크톱 응용 프로그램 만들기

  1. 메뉴 모음에서 파일, 추가, 새 프로젝트를 선택합니다.설치된 환경에서 Visual C# 노드를 확장하고 .NET 이식 가능한 라이브러리 프로젝트 템플릿을 선택한 후 프로젝트 ViewModels의 이름을 지정합니다.

  2. 참조를 추가할 F# 이식 가능한 라이브러리에 일치시키도록 이 .NET 이식 가능한 라이브러리의 대상을 설정해야 합니다.그렇지 않은 경우 오류 메시지는 불일치 사항에 대해 알려줍니다.ViewModels 프로젝트에 대한 바로 가기 메뉴에서 속성을 선택합니다.라이브러리 탭에서 이 이식 가능한 라이브러리의 대상을 .NET Framework 4.5, Silverlight 5 및 Windows 스토어 앱에 일치하도록 변경합니다.

  3. 참조 노드의 바로 가기 메뉴에서 참조 추가를 선택합니다.솔루션에서 스프레드시트 옆에 있는 확인란을 선택합니다.

  4. 다른 프로젝트 중 하나에서 ViewModels.cs의 코드를 복사하고 ViewModels 프로젝트용 코드 파일에 붙여 넣습니다.

  5. ViewModels의 코드를 UI 플랫폼과 완전히 독립적으로 만들도록 다음과 같이 변경합니다.

    1. System.Windows, System.Windows.Input, Windows.Foundation.Collections 및 Windows.UI.Xaml의 지시문을 사용하여 제거합니다(있는 경우).

    2. 네임스페이스를 ViewModels로 변경합니다.

    3. TooltipVisibility 속성을 제거합니다.이 속성은 표시 유형으로, 플랫폼별 개체를 말합니다.

  6. 메뉴 모음에서 파일, 추가, 새 프로젝트를 선택합니다.설치되었으면 Visual C# 노드를 선택한 후 WPF 응용 프로그램 프로젝트 템플릿을 선택합니다.새 프로젝트 데스크톱의 이름을 지정하고 확인 단추를 선택합니다.

  7. 데스크톱 프로젝트의 참조 노드에 대한 바로 가기 메뉴를 열고 참조 추가를 선택합니다.솔루션에서 스프레드시트 및 ViewModels 프로젝트를 선택합니다.

  8. WPF 응용 프로그램의 app.config 파일을 열고 다음 코드 줄을 추가합니다.이 코드는 .NET Framework 4.5를 대상으로 하는 데스크톱 응용 프로그램이 F#을 사용하는 .NET 이식 가능한 라이브러리를 참조할 때 적용되는 적절한 바인딩 리디렉션을 구성합니다..NET 이식 가능한 라이브러리는 FSharp.Core 라이브러리 버전 2.3.5.0을 사용하고 .NET Framework 4.5 데스크톱 응용 프로그램은 버전 4.3.0.0을 사용합니다.

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

    이제 F# 핵심 라이브러리의 이식 가능한 버전에 대한 참조를 추가해야 합니다.이 참조는 이식 가능한 라이브러리(F# 이식 가능한 라이브러리 참조)를 사용하는 응용 프로그램을 사용할 때마다 필요합니다.

  9. 데스크톱 프로젝트의 참조 노드에 대한 바로 가기 메뉴를 열고 참조 추가를 선택합니다.찾아보기를 선택한 다음 Visual Studio가 설치된 Program Files 폴더에서 Reference Assemblies\Microsoft\FSharp\3.0\Runtime\.NETPortable\FSharp.Core.dll로 이동합니다.

  10. 데스크톱 프로젝트에서 ViewModels.cs 및 Portable.Samples.Spreadsheet의 지시문을 사용하여 App.xaml.cs 및 MainWindow.xaml.cs에 추가합니다.

    using ViewModels;
    using Portable.Samples.Spreadsheet;
    
  11. MainWindow.xaml 파일을 열고 Window 창의 제목 특성을 스프레드시트로 변경합니다.

  12. Silverlight 프로젝트에서 MainPage.xaml의 표 요소 내의 코드를 복사하고 데스크톱 프로젝트에서 MainWindow.xaml의 표 요소에 해당 코드를 붙여 넣습니다.

  13. Silverlight 프로젝트에서 MainPage.xaml.cs의 이벤트 처리 코드를 복사하고 데스크톱 프로젝트에서 MainWindow.xaml.cs에 해당 코드를 붙여 넣습니다.

            void OnLostFocus(object sender, RoutedEventArgs e)
            {
                var editor = (TextBox)e.OriginalSource;
                var text = editor.Text;
    
                HideEditor(e);
    
                EditValue(editor.DataContext, text);
            }
    
            void OnKeyUp(object sender, KeyEventArgs e)
            {
                if (e.Key == Key.Escape)
                {
                    HideEditor(e);
                    e.Handled = true;
                    return;
                }
                else if (e.Key == Key.Enter)
                {
                    var editor = (TextBox)e.OriginalSource;
                    var text = editor.Text;
    
                    HideEditor(e);
    
                    EditValue(editor.DataContext, text);
                    e.Handled = true;
                }
            }
    
            private void EditValue(object dataContext, string newText)
            {
                var cvm = (CellViewModel)dataContext;
                cvm.SetCellValue(newText);
            }
    
            private void OnMouseLeftButtonDown(object sender, RoutedEventArgs e)
            {
                var textBlock = (TextBlock)e.OriginalSource;
                var editor = (TextBox)textBlock.Tag;
                textBlock.Visibility = Visibility.Collapsed;
                editor.Visibility = Visibility.Visible;
                editor.Focus();
            }
    
            private void HideEditor(RoutedEventArgs e)
            {
                var editor = (TextBox)e.OriginalSource;
                var textBlock = (TextBlock)editor.Tag;
                editor.Visibility = Visibility.Collapsed;
                textBlock.Visibility = Visibility.Visible;
            }
    
  14. 스프레드시트 시작 코드를 MainWindow.xaml.cs의 MainWindow 생성자에 추가하고 MainPage에 대한 참조를 MainWindow에 대한 참조로 바꿉니다.

        public MainWindow()
        {
                var spreadsheet = new Spreadsheet(5, 5);
                var spreadsheetViewModel = new SpreadsheetViewModel(spreadsheet);
    
    
                this.DataContext = spreadsheetViewModel;
                InitializeComponent();
        }
    
  15. 데스크톱 프로젝트의 바로 가기 메뉴를 열고 시작 프로젝트로 설정을 선택합니다.

  16. F5 키를 선택하여 앱을 빌드한 후 앱을 디버깅합니다.

다음 단계

또는 Windows 스토어 응용 프로그램 및 Silverlight 응용 프로그램용 프로젝트를 새 ViewModels 이식 가능한 라이브러리를 사용하도록 수정할 수 있습니다.

Windows 개발자 센터에서 Windows 스토어 앱에 대해 알아보십시오.

참고 항목

개념

Windows 스토어 앱

.NET Framework를 사용한 크로스 플랫폼 개발

기타 리소스

Visual F# 샘플 및 연습

Silverlight