{ "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 send code to the app while it's running.\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": null, "metadata": { "dotnet_interactive": { "language": "pwsh" }, "vscode": { "languageId": "dotnet-interactive.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": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "vscode": { "languageId": "dotnet-interactive.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": null, "metadata": { "dotnet_interactive": { "language": "mermaid" }, "vscode": { "languageId": "dotnet-interactive.mermaid" } }, "outputs": [ { "data": { "text/html": [ "
\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[\"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": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "vscode": { "languageId": "dotnet-interactive.csharp" } }, "outputs": [ { "data": { "text/html": [ "
#FF00FFFF
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "#!wpf\n", "#!dispatcher\n", "using System.Windows.Media;\n", "\n", "App.MainWindow.Background = new SolidColorBrush(Colors.Fuchsia);\n", "App.MainWindow.Background" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "vscode": { "languageId": "dotnet-interactive.csharp" } }, "outputs": [ { "data": { "text/html": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "#!wpf\n", "#!dispatcher\n", "using System.Windows.Media;\n", "using System.Windows.Controls;\n", "using System.Windows;\n", "\n", "\n", "var grid = (Grid)App.MainWindow.Content;\n", "grid.Background = new SolidColorBrush(Colors.CadetBlue);\n", "grid" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Change view models at runtime\n", "\n", "Create and apply a new view model to the main window." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "vscode": { "languageId": "dotnet-interactive.csharp" } }, "outputs": [], "source": [ "#!wpf\n", "using System.ComponentModel;\n", "public class TestViewModel : INotifyPropertyChanged\n", "{\n", " public event PropertyChangedEventHandler PropertyChanged;\n", "\n", " private string _text = \"Notebook Initial Value\";\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", "#!dispatcher\n", "App.MainWindow.DataContext = vm;" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Update the value on the data bound property." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "vscode": { "languageId": "dotnet-interactive.csharp" } }, "outputs": [ { "data": { "text/plain": [ "Value changed!" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "#!wpf\n", "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": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "vscode": { "languageId": "dotnet-interactive.csharp" } }, "outputs": [], "source": [ "#!wpf\n", "\n", "#!dispatcher --enabled true\n", "//This should work\n", "App.MainWindow.Title = \"Done correctly\";\n", "\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": "C#", "pygments_lexer": "csharp", "version": "8.0" } }, "nbformat": 4, "nbformat_minor": 4 }