{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Embedded kernels \n", "\n", "This notebook and the C# project in this folder demonstrates how you can use .NET Interactive to embed a kernel within an app, connect to it from another kernel, and use the notebook to change the app's runtime state.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "## Connect to the WPF app\n", "\n", "First, let's start the WPF app and connect to it." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "dotnet_interactive": { "language": "pwsh" } }, "outputs": [], "source": [ "Start-Process -NoNewWindow dotnet run" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Once the cell above has finished running, you should see the app's window open. Next, we'll connect to it using a named pipe. The code that sets this up within the WPF app can be seen in [`App.xaml.cs`](https://github.com/dotnet/interactive/blob/main/samples/connect-wpf/App.xaml.cs)." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "dotnet_interactive": { "language": "csharp" } }, "outputs": [ { "data": { "text/plain": [ "Kernel added: #!wpf" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "#!connect named-pipe --kernel-name wpf --pipe-name InteractiveWpf" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The topology of connected kernels now looks like this:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "dotnet_interactive": { "language": "mermaid" } }, "outputs": [ { "data": { "text/html": [ "
\r\n", "\r\n", "
\r\n", "\r\n", "
\r\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "flowchart LR\n", " subgraph WPF app\n", " embedded[\"Embedded C# kernel\"]\n", " end\n", " subgraph notebook\n", " CompositeKernel-->n1[\"Local C# kernel\"]\n", " CompositeKernel-->n2\n", " n2[\"#!wpf kernel added using #!connect\"]--named pipe-->embedded\n", " end" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Change the styling of the app\n", "\n", "The notebook outputs here are displayed using custom formatters defined within the WPF app itself. Take a look at the file [`WpfFormatterMixins.cs`](https://github.com/dotnet/interactive/blob/main/samples/connect-wpf/WpfFormatterMixins.cs).\n", "\n", "You'll also notice that you can get completions for the `App` object which is exposed to the notebook's kernel by the embedded kernel. " ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "dotnet_interactive": { "language": "wpf" }, "polyglot_notebook": { "kernelName": "wpf" } }, "outputs": [ { "data": { "text/html": [ "
#FF00FFFF
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "#!dispatcher\n", "using System.Windows.Media;\n", "\n", "App.MainWindow.Background = new SolidColorBrush(Colors.Fuchsia);\n", "App.MainWindow.Background" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "dotnet_interactive": { "language": "wpf" }, "polyglot_notebook": { "kernelName": "wpf" } }, "outputs": [ { "data": { "text/html": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "#!dispatcher\n", "using System.Windows.Media;\n", "using System.Windows.Controls;\n", "using System.Windows;\n", "\n", "var content = (Grid)App.MainWindow.Content;\n", "content.Background = new SolidColorBrush(Colors.RoyalBlue);\n", "content.UpdateLayout();\n", "content" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Change view models at runtime\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "Create and apply a new view model to the main window." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "dotnet_interactive": { "language": "wpf" }, "polyglot_notebook": { "kernelName": "wpf" } }, "outputs": [], "source": [ "using System.ComponentModel;\n", "using System.Collections.ObjectModel;\n", "\n", "public class TestViewModel : INotifyPropertyChanged\n", "{\n", " public event PropertyChangedEventHandler PropertyChanged;\n", "\n", " private string _text = \"Initial Value from notebook view model\";\n", " \n", " public string Text\n", " {\n", " get => _text;\n", " set\n", " {\n", " if (_text != value)\n", " {\n", " _text = value;\n", " PropertyChanged.Invoke(this, new PropertyChangedEventArgs(nameof(Text)));\n", " }\n", " }\n", " }\n", "}\n", "\n", "var vm = new TestViewModel();\n", "\n", "#!wpf\n", "#!dispatcher\n", "App.MainWindow.DataContext = vm;" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Update the value on the data bound property." ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "dotnet_interactive": { "language": "wpf" }, "polyglot_notebook": { "kernelName": "wpf" } }, "outputs": [ { "data": { "text/plain": [ "Value changed!" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "vm.Text = \"Value changed!\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " ## Dispatcher stuff\n", "\n", " Demonstate enabling and disabling running code on the dispatcher. " ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "dotnet_interactive": { "language": "wpf" }, "polyglot_notebook": { "kernelName": "wpf" } }, "outputs": [], "source": [ "#!dispatcher --enabled \n", "//This should work\n", "App.MainWindow.Title = \"Title change executed on dispatcher thread\";\n" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "dotnet_interactive": { "language": "wpf" }, "polyglot_notebook": { "kernelName": "wpf" } }, "outputs": [ { "ename": "Error", "evalue": "System.InvalidOperationException: The calling thread cannot access this object because a different thread owns it.\r\n at System.Windows.Threading.Dispatcher.g__ThrowVerifyAccess|7_0()\r\n at System.Windows.Application.get_MainWindow()\r\n at Submission#15.<>d__0.MoveNext()\r\n--- End of stack trace from previous location ---\r\n at Microsoft.CodeAnalysis.Scripting.ScriptExecutionState.RunSubmissionsAsync[TResult](ImmutableArray`1 precedingExecutors, Func`2 currentExecutor, StrongBox`1 exceptionHolderOpt, Func`2 catchExceptionOpt, CancellationToken cancellationToken)", "output_type": "error", "traceback": [ "System.InvalidOperationException: The calling thread cannot access this object because a different thread owns it.\r\n", " at System.Windows.Threading.Dispatcher.g__ThrowVerifyAccess|7_0()\r\n", " at System.Windows.Application.get_MainWindow()\r\n", " at Submission#15.<>d__0.MoveNext()\r\n", "--- End of stack trace from previous location ---\r\n", " at Microsoft.CodeAnalysis.Scripting.ScriptExecutionState.RunSubmissionsAsync[TResult](ImmutableArray`1 precedingExecutors, Func`2 currentExecutor, StrongBox`1 exceptionHolderOpt, Func`2 catchExceptionOpt, CancellationToken cancellationToken)" ] } ], "source": [ "\n", "#!dispatcher --enabled false\n", "//This is expected to fail\n", "App.MainWindow.Title = \"Not so much\";" ] } ], "metadata": { "kernelspec": { "display_name": ".NET (C#)", "language": "C#", "name": ".net-csharp" }, "language_info": { "file_extension": ".cs", "mimetype": "text/x-csharp", "name": "polyglot-notebook", "pygments_lexer": "csharp", "version": "8.0" }, "polyglot_notebook": { "kernelInfo": { "defaultKernelName": "csharp", "items": [ { "aliases": [], "name": "csharp" }, { "aliases": [], "languageName": "T-SQL", "name": "sql-adventureworks" }, { "aliases": [], "name": "wpf" } ] } } }, "nbformat": 4, "nbformat_minor": 4 }