チュートリアル : 汎用性のある F# ライブラリの作成
このチュートリアルに従うと、Silverlight アプリケーション、従来のデスクトップ アプリケーション、または .NET API を使用して作成する Windows ストア アプリで使用できるアセンブリを F# で作成できます。この方法では、C#、Visual Basic などの別の .NET 言語でアプリケーションの UI 部分を記述し、F# でアルゴリズム部分を記述できます。また、異なるプラットフォームをターゲットとするさまざまなユーザー インターフェイスもサポートできます。
Windows ストア UI を直接 F# から使用することはできないため、別の .NET 言語で Windows ストア アプリの UI を記述し、ポータブル ライブラリで F# コードを記述することをお勧めします。Silverlight および Windows Presentation Foundation (WPF) の UI は直接 F# で記述できますが、C# または Visual Basic のコードを Visual Studio で記述するときに使用できるその他のデザイン ツールを利用することもできます。
必須コンポーネント
Windows ストア アプリを作成するには、開発コンピューターに Windows 8 がインストールされている必要があります。
Silverlight プロジェクトを作成するには、開発コンピューターに Silverlight 5 がインストールされている必要があります。
スプレッドシート アプリケーション
このチュートリアルでは、グリッドを表示して、そのセルへの数値入力と数式を受け入れる単純なスプレッドシートを作成します。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# ポータブル ライブラリを作成する
メニュー バーで [ファイル]、[新しいプロジェクト] を順にクリックします。[新しいプロジェクト] ダイアログ ボックスで、[Visual F#] を展開し、プロジェクトの種類として [F# ポータブル ライブラリ] を選択して、ライブラリに「Spreadsheet」という名前を付けます。プロジェクトが FSharp.Core の特別なバージョンを参照していることに注目してください。
ソリューション エクスプローラーで、[参照] ノードを展開し、[FSharp.Core] ノードをクリックします。[プロパティ] ウィンドウで、FullPath プロパティの値が .NETPortable になっています。これは、F# コア ライブラリのポータブル バージョンを使用していることを示します。また、既定でアクセスできる .NET の各種ライブラリも確認できます。これらのライブラリはすべて .NET ポータブルと定義された .NET Framework の共通サブセットと連動します。不要な参照は削除できますが、参照を追加する場合は、ターゲットとするすべてのプラットフォームでその参照アセンブリが使用可能である必要があります。通常、アセンブリのドキュメントに、そのアセンブリを使用できるプラットフォームが示されています。
プロジェクトのショートカット メニューを開き、[プロパティ] をクリックします。[アプリケーション] タブで、ターゲット フレームワークを [.NET ポータブル サブセット] に設定します。Visual Studio 2012 の場合、このサブセットのターゲットは Windows ストア アプリ、.NET Framework 4.5、および Silverlight 5 用の .NET です。作成するアプリケーションはポータブル ライブラリとしてさまざまなプラットフォームで使用できるランタイムに対して実行する必要があるため、これらの設定は重要です。Windows ストア アプリと Silverlight 5 のランタイムには完全な .NET Framework のサブセットが含まれています。
メイン コード ファイル 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 アプリケーションを作成する
メニュー バーで [ファイル] をクリックし、[追加] を選択し、[新しいプロジェクト] をクリックします。[新しいプロジェクトの追加] ダイアログ ボックスで、[Visual C++] を展開し、[Silverlight] を展開して、[Silverlight アプリケーション] をクリックします。[新しい Silverlight アプリケーション] ダイアログ ボックスが表示されます。
[Silverlight アプリケーションを新しい Web サイトでホストする] チェック ボックスがオンになっていることを確認し、ドロップダウンで [ASP.NET Web アプリケーション プロジェクト] が選択されていることを確認してから、[OK] をクリックします。プロジェクトが 2 つ作成されます。一方のプロジェクトには Silverlight コントロールが含まれ、もう一方のプロジェクトは、そのコントロールをホストする ASP.NET Web アプリケーションです。
スプレッドシート プロジェクトへの参照を追加します。Silverlight プロジェクトの [参照] ノードのショートカット メニューを開き、[参照の追加] をクリックします。参照マネージャーが表示されます。[ソリューション] ノードを展開し、スプレッドシート プロジェクトを選択して、[OK] をクリックします。
この手順ではビュー モデルを作成します。ビュー モデルは、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)); } } }
Silverlight コントロール プロジェクトで、メイン スプレッドシートの UI レイアウトを宣言する MainPage.xaml を開きます。MainPage.xaml で、既存のグリッド要素に次の 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>
MainPage.xaml.cs で、using SilverlightFrontEnd; を using ディレクティブの一覧に追加し、次のメソッドを 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; }
App.xaml.cs で、次の using ディレクティブを追加します。
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;
Silverlight プロジェクトを直接起動するか、Silverlight コントロールをホストする ASP.NET Web アプリケーションを起動して、Silverlight のフロント エンドをテストできます。どちらかのプロジェクトのノードのショートカット メニューを開き、[スタートアップ プロジェクトに設定] をクリックします。
方法: F# ポータブル ライブラリを使用する Windows ストア アプリを作成する
このセクションでは、計算コンポーネントとして F# のスプレッドシート コードを使用する Windows ストア アプリを作成します。メニュー バーで [ファイル] をクリックし、[追加] を選択し、[新しいプロジェクト] をクリックします。[新しいプロジェクト] ダイアログ ボックスが表示されます。[インストール済み] の下で、[Visual C#]、[Windows ストア] の順に展開し、[空のアプリケーション] テンプレートを選択します。プロジェクトに「NewFrontEnd」という名前を付けて [OK] をクリックします。Windows ストア アプリを作成するための開発者ライセンスを要求された場合は、資格情報を入力します。資格情報がない場合は、こちらでその設定方法を確認してください。
プロジェクトが作成されます。このプロジェクトの構成と内容に注目してください。既定の参照には、Windows ストア アプリと互換性がある .NET Framework のサブセットである Windows ストア アプリ用 .NET と、Windows ランタイム用 API と Windows ストア アプリ用 UI を含む Windows アセンブリが含まれています。資産サブフォルダーと共通サブフォルダーが作成されました。資産サブフォルダーには、Windows ストア アプリに適用されるいくつかのアイコンが含まれ、共通サブフォルダーには、Windows ストア アプリで使用するテンプレートとなる共通のルーチンが含まれています。既定のプロジェクト テンプレートによって、App.xaml、BlankPage.xaml、およびそれらに関連する C# 分離コード ファイルとして App.xaml.cs と BlankPage.xaml.cs も作成されました。App.xaml はアプリケーション全体について記述し、BlankPage.xaml は 1 つの定義済み UI サーフェイスについて記述します。最後に、すべての .pfx ファイルと .appxmanifest ファイルは Windows ストア アプリのセキュリティ モデルと配置モデルをサポートします。
Silverlight プロジェクトの [参照] ノードのショートカット メニューを開き、[参照の追加] をクリックして、スプレッドシート プロジェクトへの参照を追加します。参照マネージャーで [ソリューション] ノードを展開し、スプレッドシート プロジェクトを選択して、[OK] をクリックします。
Windows ストア アプリの UI のコードをサポートするには、Silverlight プロジェクトで既に使用したコードの一部が必要です。このコードは ViewModels.cs に含まれています。NewFrontEnd のプロジェクト ノードのショートカット メニューを開き、[追加] をクリックし、[新しい項目] を選択します。C# コード ファイルを追加し、「ViewModels.cs」という名前を付けます。Silverlight プロジェクトの ViewModels.cs からコードを貼り付け、このファイルの先頭にある using ディレクティブのブロックを変更します。Silverlight の UI に使用される System.Windows を削除し、Windows ストア アプリの UI に使用される Windows.UI.Xaml と Windows.Foundation.Collections を追加します。Silverlight も Windows ストア UI も WPF に基づいているため、互換性があります。更新された using ディレクティブ ブロックは、次の例のようになります。
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;
また、ViewModels.cs の名前空間を SilverlightFrontEnd から NewFrontEnd に変更します。
ViewModels.cs のその他のコードは再利用できますが、Visibility など、一部の型は Silverlight ではなく、Windows ストア アプリのバージョンです。
この Windows ストア アプリでは、Silverlight アプリケーションの Application_Startup イベント ハンドラーに含まれているコードとよく似たスタートアップ コードが App.xaml.cs コード ファイルに必要です。Windows ストア アプリでは、このコードは、アプリ クラスの OnLaunched イベント ハンドラーに含まれています。次のコードを App.xaml.cs の OnLaunched イベント ハンドラーに追加します。
var spreadsheet = new Spreadsheet(5, 5); var spreadsheetViewModel = new SpreadSheetViewModel(spreadsheet);
スプレッドシート コードの using ディレクティブを追加します。
using Portable.Samples.Spreadsheet;
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 とその分離コード ファイルはこの例で使用されないため、削除できます。
NewFrontEnd のプロジェクト ノードのショートカット メニューを開き、[追加] をクリックし、[新しい項目] を選択します。項目ページを追加し、既定の名前 ItemsPage1.xaml をそのままにします。この手順では、ItemsPage1.xaml と、その分離コード ファイル ItemsPage1.xaml.cs を両方ともプロジェクトに追加します。ItemsPage1.xaml は、次の XAML コードが示すように、多数の属性を持つ common:LayoutAwarePage のメイン タグから始まります。
<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 形式は同じです。したがって、Silverlight プロジェクトの MainPage.xaml の XAML を Windows ストア アプリの UI の ItemsPage1.xaml に再利用できます。
Silverlight プロジェクトの MainPage.xaml のトップレベル グリッド要素内のコードをコピーし、Windows ストア アプリの UI 用にプロジェクトの ItemsPage1.xaml のトップレベル グリッド要素に貼り付けます。コードを貼り付けるとき、グリッド要素の既存の内容を上書きできます。グリッド要素のバックグラウンド属性を「白」に変更し、MouseLeftButtonDown を PointerPressed に置き換えます。
このイベントの名前は、Silverlight アプリケーションと Windows ストア アプリで異なります。
ItemsPage.xaml.cs で、OnNavigatedTo メソッドを変更して DataContext プロパティを設定します。
protected override void OnNavigatedTo(NavigationEventArgs e) { this.DataContext = e.Parameter; }
次のイベントハンドラー コードをコピーし、ItemsPage1 クラス OnLostFocus、OnKeyUp、EditValue、OnPointerPressed、および 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; }
スタートアップ プロジェクトを Windows ストア アプリのプロジェクトに変更します。[NewFrontEnd] プロジェクト ノードのショートカット メニューを開き、[スタートアップ プロジェクトに設定] をクリックし、F5 キーを押してプロジェクトを実行します。
C# で F# を使用するポータブル ライブラリを作成する
前の例では、ViewModels.cs コードが複数のプロジェクトに含まれているのでコードを複製しました。このセクションでは、このコードを含む C# ポータブル ライブラリ プロジェクトを作成します。場合によっては、F# を使用するポータブル ライブラリを使用する場合にアプリケーションの構成ファイルに情報を追加する必要があります。この場合、.NET Framework 4.5 のデスクトップ バージョンをターゲットとするデスクトップ アプリケーションは、同様に F# ポータブル ライブラリを参照する C# ポータブル ライブラリを参照します。このような場合は、メイン アプリケーションの app.config ファイルにバインディング リダイレクトを追加する必要があります。FSharp.Core ライブラリの 1 バージョンのみが読み込まれても、ポータブル ライブラリが .NET ポータブル バージョンを参照するため、このリダイレクトを追加する必要があります。FSharp.Core 関数の .NET ポータブル バージョンに対する呼び出しは、デスクトップ アプリケーションに読み込まれた FSharp.Core の単一バージョンにリダイレクトされる必要があります。バインド リダイレクトはデスクトップ アプリケーションでのみ必要です。その理由は、Silverlight 5 アプリケーションと Windows ストア アプリのランタイム環境では、完全なデスクトップ バージョンではなく、FSharp.Core の .NET ポータブル バージョンが使用されるからです。
方法: F# を使用するポータブル ライブラリを参照するデスクトップ アプリケーションを作成する
メニュー バーで [ファイル] をクリックし、[追加] を選択し、[新しいプロジェクト] をクリックします。[インストール済み] の下で、[Visual C#] ノードを展開し、[.NET Portable Library](.NET ポータブル ライブラリ) のプロジェクト テンプレートを選択して、プロジェクトに「ViewModels」という名前を付けます。
参照の追加先の F# ポータブル ライブラリと一致するように、この .NET ポータブル ライブラリのターゲットを設定する必要があります。設定しないと、不一致を示すエラー メッセージが表示されます。ViewModels プロジェクトのショートカット メニューを開き、[プロパティ] をクリックします。[ライブラリ] タブで、.NET Framework 4.5、Silverlight 5、および Windows ストア アプリに合うように、このポータブル ライブラリのターゲットを変更します。
[参照] ノードのショートカット メニューで、[参照の追加] をクリックします。[ソリューション] の下で、[スプレッドシート] の横にあるチェック ボックスをオンにします。
他のプロジェクトの 1 つから ViewModels.cs のコードをコピーし、ViewModels プロジェクトのコード ファイルに貼り付けます。
次の変更を行い、ViewModels のコードを UI プラットフォームから完全に独立させます。
System.Windows、System.Windows.Input、Windows.Foundation.Collections、および Windows.UI.Xaml の using ディレクティブがある場合は、それらを削除します。
名前空間を ViewModels に変更します。
TooltipVisibility プロパティを削除します。このプロパティは、プラットフォーム依存オブジェクトである Visibility を使用します。
メニュー バーで [ファイル] をクリックし、[追加] を選択し、[新しいプロジェクト] をクリックします。[インストール済み] の下で、[Visual C#] ノードを展開し、WPF アプリケーションのプロジェクト テンプレートを選択します。新しいプロジェクトに「Desktop」という名前を付けて [OK] をクリックします。
デスクトップ プロジェクトの [参照] ノードのショートカット メニューを開き、[参照の追加] をクリックします。[ソリューション] の下で、Spreadsheet プロジェクトと ViewModels プロジェクトを選択します。
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# ポータブル ライブラリを参照するポータブル ライブラリを使用するアプリケーションがある場合は常に必要です。
デスクトップ プロジェクトの [参照] ノードのショートカット メニューを開き、[参照の追加] をクリックします。[参照] をクリックし、Visual Studio がインストールされている Program Files フォルダーの下の Reference Assemblies\Microsoft\FSharp\3.0\Runtime\.NETPortable\FSharp.Core.dll に移動します。
デスクトップ プロジェクトで、ViewModels.cs の using ディレクティブと Portable.Samples.Spreadsheet を App.xaml.cs および MainWindow.xaml.cs に追加します。
using ViewModels; using Portable.Samples.Spreadsheet;
MainWindow.xaml ファイルを開き、ウィンドウ クラスのタイトル属性を Spreadsheet に変更します。
Silverlight プロジェクトの MainPage.xaml のグリッド要素内のコードをコピーし、デスクトップ プロジェクトの MainWindow.xaml のグリッド要素に貼り付けます。
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; }
スプレッドシート スタートアップ コードを MainWindow.xaml.cs の MainWindow コンストラクターに追加し、MainPage への参照を MainWindow への参照に置き換えます。
public MainWindow() { var spreadsheet = new Spreadsheet(5, 5); var spreadsheetViewModel = new SpreadsheetViewModel(spreadsheet); this.DataContext = spreadsheetViewModel; InitializeComponent(); }
デスクトップ プロジェクトのショートカット メニューを開き、[スタートアップ プロジェクトに設定] をクリックします。
F5 キーを押してアプリケーションをビルドしてから、デバッグします。
次の手順
または、新しい ViewModels ポータブル ライブラリを使用するように Windows ストア アプリケーションと Silverlight アプリケーションのプロジェクトを変更することもできます。
Windows ストア アプリケーションについては、Windows デベロッパー センターで引き続き学習できます。
参照
概念
.NET Framework を使用したプラットフォーム間の開発