Skip to content

Commit 5265970

Browse files
authored
Wpf example (#666)
* Initial checkin of code from stream * Cleaning up example code * Updating to leverage PocketView DSL
1 parent b2079c8 commit 5265970

11 files changed

+385
-0
lines changed

samples/connect-wpf/App.xaml

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<Application x:Class="WpfConnect.App"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:local="clr-namespace:WpfConnect"
5+
StartupUri="MainWindow.xaml">
6+
<Application.Resources>
7+
8+
</Application.Resources>
9+
</Application>

samples/connect-wpf/App.xaml.cs

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
using Microsoft.DotNet.Interactive;
2+
using Microsoft.DotNet.Interactive.Commands;
3+
using Microsoft.DotNet.Interactive.CSharp;
4+
using Microsoft.DotNet.Interactive.Server;
5+
using System.CommandLine;
6+
using System.CommandLine.Invocation;
7+
using System.IO;
8+
using System.Threading.Tasks;
9+
using System.Windows;
10+
11+
namespace WpfConnect
12+
{
13+
/// <summary>
14+
/// Interaction logic for App.xaml
15+
/// </summary>
16+
public partial class App : Application
17+
{
18+
private CompositeKernel _Kernel;
19+
20+
private const string NamedPipeName = "InteractiveWpf";
21+
22+
private bool RunOnDispatcher { get; set; }
23+
24+
protected override void OnStartup(StartupEventArgs e)
25+
{
26+
base.OnStartup(e);
27+
_Kernel = new CompositeKernel();
28+
_Kernel.UseLog();
29+
30+
AddDispatcherCommand(_Kernel);
31+
32+
CSharpKernel csharpKernel = RegisterCSharpKernel();
33+
34+
_ = Task.Run(async () =>
35+
{
36+
//Load WPF app assembly
37+
await csharpKernel.SendAsync(new SubmitCode(@$"#r ""{typeof(App).Assembly.Location}""
38+
using {nameof(WpfConnect)};"));
39+
//Add the WPF app as a variable that can be accessed
40+
await csharpKernel.SetVariableAsync("App", this);
41+
42+
//Start named pipe
43+
_Kernel.UseNamedPipeKernelServer(NamedPipeName, new DirectoryInfo("."));
44+
});
45+
}
46+
47+
protected override void OnExit(ExitEventArgs e)
48+
{
49+
_Kernel?.Dispose();
50+
base.OnExit(e);
51+
}
52+
53+
private void AddDispatcherCommand(Kernel kernel)
54+
{
55+
var dispatcherCommand = new Command("#!dispatcher", "Enable or disable running code on the Dispatcher")
56+
{
57+
new Option<bool>("--enabled", getDefaultValue:() => true)
58+
};
59+
dispatcherCommand.Handler = CommandHandler.Create<bool>(enabled =>
60+
{
61+
RunOnDispatcher = enabled;
62+
});
63+
kernel.AddDirective(dispatcherCommand);
64+
}
65+
66+
private CSharpKernel RegisterCSharpKernel()
67+
{
68+
var csharpKernel = new CSharpKernel()
69+
.UseDefaultFormatting()
70+
.UseNugetDirective()
71+
.UseKernelHelpers()
72+
.UseWho()
73+
.UseDotNetVariableSharing()
74+
//This is added locally
75+
.UseWpf();
76+
77+
_Kernel.Add(csharpKernel);
78+
79+
csharpKernel.AddMiddleware(async (KernelCommand command, KernelInvocationContext context, KernelPipelineContinuation next) =>
80+
{
81+
if (RunOnDispatcher)
82+
{
83+
await Dispatcher.InvokeAsync(async () => await next(command, context));
84+
}
85+
else
86+
{
87+
await next(command, context);
88+
}
89+
});
90+
91+
return csharpKernel;
92+
}
93+
}
94+
}

samples/connect-wpf/AssemblyInfo.cs

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System.Windows;
2+
3+
[assembly: ThemeInfo(
4+
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
5+
//(used if a resource is not found in the page,
6+
// or application resource dictionaries)
7+
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
8+
//(used if a resource is not found in the page,
9+
// app, or any theme specific resource dictionaries)
10+
)]

samples/connect-wpf/MainWindow.xaml

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<Window x:Class="WpfConnect.MainWindow"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
5+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
6+
mc:Ignorable="d"
7+
Title="MainWindow" Height="450" Width="800">
8+
<Grid>
9+
<TextBlock Text="{Binding Text}" />
10+
</Grid>
11+
</Window>
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using Microsoft.DotNet.Interactive;
2+
using System.Windows;
3+
4+
namespace WpfConnect
5+
{
6+
/// <summary>
7+
/// Interaction logic for MainWindow.xaml
8+
/// </summary>
9+
public partial class MainWindow : Window
10+
{
11+
public MainWindow()
12+
{
13+
InitializeComponent();
14+
DataContext = new MainWindowViewModel();
15+
}
16+
}
17+
}
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System.ComponentModel;
2+
3+
namespace WpfConnect
4+
{
5+
public class MainWindowViewModel : INotifyPropertyChanged
6+
{
7+
public event PropertyChangedEventHandler PropertyChanged;
8+
9+
private string _text = "Init Value";
10+
public string Text
11+
{
12+
get => _text;
13+
set
14+
{
15+
if (_text != value)
16+
{
17+
_text = value;
18+
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(nameof(Text)));
19+
}
20+
}
21+
}
22+
}
23+
}

samples/connect-wpf/NuGet.config

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<configuration>
3+
<solution>
4+
<add key="disableSourceControlIntegration" value="true" />
5+
</solution>
6+
<packageSources>
7+
<clear />
8+
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
9+
<add key="dotnet5" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5/nuget/v3/index.json" />
10+
<add key="dotnet-tools" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json" />
11+
<add key="dotnet-try" value="https://dotnet.myget.org/F/dotnet-try/api/v3/index.json" />
12+
</packageSources>
13+
</configuration>

samples/connect-wpf/README.dib

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
#!markdown
2+
3+
## Connect to the WPF app
4+
Before running cell, ensure the WPF app is running. This will connect the notebook to the WPF app. This must be run before any of the other cells.
5+
6+
#!csharp
7+
8+
#!connect named-pipe --kernel-name wpf --pipe-name InteractiveWpf
9+
10+
#!markdown
11+
12+
## Formatter (Rendering)
13+
14+
#!csharp
15+
16+
#!wpf
17+
#!dispatcher
18+
using System.Windows.Media;
19+
20+
App.MainWindow.Background = new SolidColorBrush(Colors.Fuchsia);
21+
App.MainWindow.Background
22+
23+
#!csharp
24+
25+
#!wpf
26+
#!dispatcher
27+
using System.Windows.Media;
28+
using System.Windows.Controls;
29+
using System.Windows;
30+
31+
32+
var grid = (Grid)App.MainWindow.Content;
33+
grid.Background = new SolidColorBrush(Colors.Blue);
34+
grid
35+
36+
#!markdown
37+
38+
## View Model Stuff
39+
40+
Create and apply a new view model to the main window.
41+
42+
#!csharp
43+
44+
#!wpf
45+
using System.ComponentModel;
46+
public class TestViewModel : INotifyPropertyChanged
47+
{
48+
public event PropertyChangedEventHandler PropertyChanged;
49+
50+
private string _text = "Notebook Initial Value";
51+
public string Text
52+
{
53+
get => _text;
54+
set
55+
{
56+
if (_text != value)
57+
{
58+
_text = value;
59+
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(nameof(Text)));
60+
}
61+
}
62+
}
63+
}
64+
65+
var vm = new TestViewModel();
66+
#!dispatcher
67+
App.MainWindow.DataContext = vm;
68+
69+
#!markdown
70+
71+
Update the value on the data bound property.
72+
73+
#!csharp
74+
75+
#!wpf
76+
vm.Text = "Value changed!"
77+
78+
#!markdown
79+
80+
## Dispatcher stuff
81+
82+
Demonstate enabling and disabling running code on the dispatcher.
83+
84+
#!csharp
85+
86+
#!wpf
87+
88+
#!dispatcher --enabled true
89+
//This should work
90+
App.MainWindow.Title
91+
92+
#!dispatcher --enabled false
93+
//This is expected to fail
94+
App.MainWindow.Title

samples/connect-wpf/WpfConnect.csproj

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netcoreapp3.1</TargetFramework>
5+
<OutputType>WinExe</OutputType>
6+
<UseWPF>true</UseWPF>
7+
<DisableArcade>1</DisableArcade>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="Microsoft.DotNet.Interactive.CSharp" Version="1.0.0-beta.20374.1" />
12+
</ItemGroup>
13+
14+
</Project>

samples/connect-wpf/WpfConnect.sln

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 16
4+
VisualStudioVersion = 16.0.30310.162
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfConnect", "WpfConnect.csproj", "{3DE53DCF-00AD-4614-9CFD-A342317E7347}"
7+
EndProject
8+
Global
9+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|Any CPU = Debug|Any CPU
11+
Release|Any CPU = Release|Any CPU
12+
EndGlobalSection
13+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
14+
{3DE53DCF-00AD-4614-9CFD-A342317E7347}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15+
{3DE53DCF-00AD-4614-9CFD-A342317E7347}.Debug|Any CPU.Build.0 = Debug|Any CPU
16+
{3DE53DCF-00AD-4614-9CFD-A342317E7347}.Release|Any CPU.ActiveCfg = Release|Any CPU
17+
{3DE53DCF-00AD-4614-9CFD-A342317E7347}.Release|Any CPU.Build.0 = Release|Any CPU
18+
EndGlobalSection
19+
GlobalSection(SolutionProperties) = preSolution
20+
HideSolutionNode = FALSE
21+
EndGlobalSection
22+
GlobalSection(ExtensibilityGlobals) = postSolution
23+
SolutionGuid = {18629B67-9CB9-4089-A138-6C2BE0D43500}
24+
EndGlobalSection
25+
EndGlobal
+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
using Microsoft.DotNet.Interactive.CSharp;
2+
using Microsoft.DotNet.Interactive.Formatting;
3+
using System;
4+
using System.IO;
5+
using System.Windows;
6+
using System.Windows.Media;
7+
using System.Windows.Media.Imaging;
8+
9+
using static Microsoft.DotNet.Interactive.Formatting.PocketViewTags;
10+
11+
namespace WpfConnect
12+
{
13+
public static class WpfFormatterMixins
14+
{
15+
public static CSharpKernel UseWpf(this CSharpKernel kernel)
16+
{
17+
UseSolidColorBrushFormatter();
18+
UseFrameworkElementFormatter();
19+
return kernel;
20+
}
21+
22+
private static void UseSolidColorBrushFormatter()
23+
{
24+
Formatter<SolidColorBrush>.Register((brush, writer) =>
25+
{
26+
var color = brush.Color;
27+
string stringValue = $"#{color.R:X2}{color.G:X2}{color.B:X2}{color.A:X2}";
28+
29+
PocketView colorDiv = div(
30+
div[style: $"border:2px solid #FFFFFF;background-color:{stringValue};width:15px;height:15px"](),
31+
div(b(stringValue))
32+
);
33+
writer.Write(colorDiv);
34+
35+
}, "text/html");
36+
}
37+
38+
private static void UseFrameworkElementFormatter()
39+
{
40+
Formatter.Register(type: typeof(IFrameworkInputElement), formatter: (visual, writer) => {
41+
if (visual is FrameworkElement element)
42+
{
43+
writer.Write(GetImage(element));
44+
}
45+
}, "text/html");
46+
}
47+
48+
private static PocketView GetImage(FrameworkElement visual)
49+
{
50+
var rect = new Rect(visual.RenderSize);
51+
var drawingVisual = new DrawingVisual();
52+
53+
using (var dc = drawingVisual.RenderOpen())
54+
{
55+
dc.DrawRectangle(new VisualBrush(visual), null, rect);
56+
}
57+
58+
var bitmap = new RenderTargetBitmap(
59+
(int)rect.Width, (int)rect.Height, 96, 96, PixelFormats.Default);
60+
bitmap.Render(drawingVisual);
61+
62+
var encoder = new PngBitmapEncoder();
63+
encoder.Frames.Add(BitmapFrame.Create(bitmap));
64+
65+
using var ms = new MemoryStream();
66+
encoder.Save(ms);
67+
ms.Flush();
68+
var data = ms.ToArray();
69+
var imageSource = $"data:image/png;base64, {Convert.ToBase64String(data)}";
70+
71+
PocketView png = img[src: imageSource, width: rect.Width, height: rect.Height]();
72+
return png;
73+
}
74+
}
75+
}

0 commit comments

Comments
 (0)