https://github.com/sjefvanleeuwen/wpf-blazor-porting
Small demonstrator to show porting WPF to Blazor
https://github.com/sjefvanleeuwen/wpf-blazor-porting
blazor conversion converter porting silverlight wpf wpf-to-blazor wpftoblazor
Last synced: about 1 month ago
JSON representation
Small demonstrator to show porting WPF to Blazor
- Host: GitHub
- URL: https://github.com/sjefvanleeuwen/wpf-blazor-porting
- Owner: sjefvanleeuwen
- License: mit
- Created: 2020-12-10T21:45:11.000Z (over 4 years ago)
- Default Branch: main
- Last Pushed: 2020-12-11T11:11:44.000Z (over 4 years ago)
- Last Synced: 2024-10-06T08:41:21.159Z (8 months ago)
- Topics: blazor, conversion, converter, porting, silverlight, wpf, wpf-to-blazor, wpftoblazor
- Language: C#
- Homepage:
- Size: 323 KB
- Stars: 4
- Watchers: 2
- Forks: 4
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# wpf-blazor-porting
## What did I do?
I Built a small demonstrator showing porting WPF to Blazor (Web Assembly) using a user interaction pattern supported by some business logic. In this demonstrator we:
* Ported a simple calculator
* Mimick the WPF XAML grid system, button, textbox and textlist as a Razor Components
* Translate these to wrap to Blazorize
* Slightly alter the WPF code behind and XAML for proper databinding.## Why did I do it?
Firstly, a lot of great legacy software was built in Silverlight and WPF. Although these are great frameworks, they don't adhere to standards such as (Isolated) CSS, HTML and Web Assembly. The Latter paves the way to running User Interface Native on any device in any programming language (though we focus on C# here).
Secondly, serving WPF is very costly to the consumer, as these need to be delivered through (Virtualized) Windows Desktops and can not run in the browser. Silverlight was
able to run on some devices but not all. Microsoft Silverlight will reach the end of support on October 12, 2021. Silverlight development framework is currently only supported on Internet Explorer 10 and Internet Explorer 11, with support for Internet Explorer 10 ending on January 31, 2020.Thirdly, Web Assembly enables legacy applications to offer a good road map in transitioning. A lot of logic can be reused transparently both on client and server. This enables us to offload server side logic to the client and make interactions more smoothly for the end-user while at the same time lowering the total cost of owner ship at bare metal in
the cloud or your data center.This proof of concept exercise herein took about half a day to realize. If you understand WPF, the learning curve to Blazor Utilizing Web Assembly is a welcoming one (I Speak
from experience).## Side Notes
Using Web Assembly technology we can reduce licensing fee's in SAAS solutions to the consumer *both on bare metal servers/cloud infrastructure* as well as delivery to thin clients in comparison to WPF based applications. While remaining in the same programming paradigm, which is highly beneficial to DevOps. Ultimately Web Assmeblies are very
capable being part of the IoT cloud and can easily integrate with decentralized computing models.### Styling
*Note* We did not set a style-sheet for font and colors, font size in bootstrap is a bit bigger by default.
With some alterations, auto overflow/font size it will become pixel perfect.### State management, vertical slicing & micro front ends.
Also notice the EventCallback differences, as well as Add.Item AddItem, Blazor needs more binding lambda's to bind to complex objects. This could also be achieved by Cascading Parameters. Or, more preferable, a good State and Mediator pattern that supports **vertical slicing** for **micro front ends.**
Such as **Blazor-State** from my friends at Timewarp Engineering. https://timewarpengineering.github.io/blazor-state/
### Front end, Code behind and Business Logic reuse
Front end and Code Behind reuse reaches well **over 90% reuse** differences are included in diffing reports.
Business Logic reuse (in this case Calc class instances) were **100% compatible** and therefore **not** included in diffing report.
### The original WPF application
Application is ported from Anna Pavlenko: https://github.com/apavlen WPF-Calculator
### Differences (standard Blazorise styling)
#### Blazor
#### WPF
## How did I do it?
Lets get to the nitty gritty :)
### Setting the stage with Isomorphic Design Patterns
A lot of WPF components, actually all in this example, have a common ancestry in their attributes.
These are captured in a WpfStyleBase class so every component can reuse them as **isomorphic design patterns.**#### The beginning of a Union abstract in translating WPF -> HTML styles
Consider this base class:
```CSharp
public abstract class WpfStyleBase : ComponentBase
{
[Parameter]
public RenderFragment ChildContent { get; set; }
[Parameter]
public int Height { get; set; }
[Parameter]
public string HorizontalAlignment { get; set; }
[Parameter]
public string Margin { get; set; }
[Parameter]
public string VerticalAlignment { get; set; }
[Parameter]
public int Width { get; set; }public string Style
{
get
{
// WPF Margin is in left,top,right,bottom
// CSS Margin is in top, right, bottom, left
var o = Margin.Split(',');
return $"position:absolute; margin: {o[1]}px {o[2]}px {o[3]}px {o[0]}px; width: {Width}px; height: {Height}px; vertical-align: {VerticalAlignment}; text-align: {HorizontalAlignment};";
}
}
}
```*Note* The orientation of WPF v.s. HTML for margin's can be **translated within the abstraction**. The HTMl style is build up accordingly.
#### Minimalistic wrappers between XAML and Razor
Because of said abstraction and the use of a good Blazor Framework, such as blazorise, the razor components become **extremely** minimized.
For example:##### Grid System
```XML
@inherits WpfStyleBase@ChildContent
```##### Text Box
```XML
@inherits WpfStyleBase```
##### Button
```Button
@inherits WpfStyleBase
@Content
```The properties of these controls are propagated as parameters and can be **effectively** described by the
Razor Component models.Time for some Diffs.
## Diff between XAML/Razor
```diff
-
-
+
+
```## Diff between Code Behind
```diff
static string defaultString = "0.0";
string numberOneBuffer;
string numberTwoBuffer;
Calc c;
+ IText textBox1 { get; set; }
+ IItems listBox1 { get; set; }- public MainWindow()
+ protected override void OnAfterRender(bool firstRender)
{
- InitializeComponent();
- c = new Calc();
- textBox1.Text = defaultString;
+ if (firstRender)
+ {
+ textBox1.Text = defaultString;
+ c = new Calc();
numberOneBuffer = "";
numberTwoBuffer = "";
+ }
}
- private void one_Click(object sender, RoutedEventArgs e)
+ private void one_Click(MouseEventArgs e)
{
+ Console.WriteLine("My debug output.");
numberOneBuffer += "1";
textBox1.Text = numberOneBuffer;
}
- private void two_Click(object sender, RoutedEventArgs e)
+ private void two_Click(MouseEventArgs e)
{
numberOneBuffer += "2";
textBox1.Text = numberOneBuffer;
}
- private void three_Click(object sender, RoutedEventArgs e)
+ private void three_Click(MouseEventArgs e)
{
numberOneBuffer += "3";
textBox1.Text = numberOneBuffer;
}
- private void four_Click(object sender, RoutedEventArgs e)
+ private void four_Click(MouseEventArgs e)
{
numberOneBuffer += "4";
textBox1.Text = numberOneBuffer;
}
- private void five_Click(object sender, RoutedEventArgs e)
+ private void five_Click(MouseEventArgs e)
{
numberOneBuffer += "5";
textBox1.Text = numberOneBuffer;
}
- private void six_Click(object sender, RoutedEventArgs e)
+ private void six_Click(MouseEventArgs e)
{
numberOneBuffer += "6";
textBox1.Text = numberOneBuffer;
}
- private void seven_Click(object sender, RoutedEventArgs e)
+ private void seven_Click(MouseEventArgs e)
{
numberOneBuffer += "7";
textBox1.Text = numberOneBuffer;
}
- private void eight_Click(object sender, RoutedEventArgs e)
+ private void eight_Click(MouseEventArgs e)
{
numberOneBuffer += "8";
textBox1.Text = numberOneBuffer;
}
- private void nine_Click(object sender, RoutedEventArgs e)
+ private void nine_Click(MouseEventArgs e)
{
numberOneBuffer += "9";
textBox1.Text = numberOneBuffer;
}
- private void zero_Click(object sender, RoutedEventArgs e)
+ private void zero_Click(MouseEventArgs e)
{
numberOneBuffer += "0";
textBox1.Text = numberOneBuffer;
}
- private void decipoint_Click(object sender, RoutedEventArgs e)
+ private void decipoint_Click(MouseEventArgs e)
{
numberOneBuffer += ".";
}
- private void enter_Click(object sender, RoutedEventArgs e)
+ private void enter_Click(MouseEventArgs e)
{
string answer = "";
if (c.isFirstOperation() || ((numberOneBuffer.Length > 0) && (numberTwoBuffer.Length > 0)))
{
answer = c.operate(Convert.ToDouble(numberTwoBuffer), Convert.ToDouble(numberOneBuffer));
}
else
{
answer = c.operate(Convert.ToDouble(numberTwoBuffer));
}
textBox1.Text = answer;
updateList(answer);
numberOneBuffer = "";
numberTwoBuffer = "";
}
private void updateList(string answer)
{
string s = numberTwoBuffer;
Calc.Operators op = c.getOperation();
switch (op)
{
case Calc.Operators.Addition:
s += " + ";
break;
case Calc.Operators.Subtraction:
s += " - ";
break;
case Calc.Operators.Multiplication:
s += " x ";
break;
case Calc.Operators.Division:
s += " % ";
break;
}
s += numberOneBuffer;
s += " = ";
s += answer;
- listBox1.Items.Add(s);
+ listBox1.AddItem(s);
}
- private void sign_Click(object sender, RoutedEventArgs e)
+ private void sign_Click(MouseEventArgs e)
{
if (numberOneBuffer.Length > 0)
{
if (numberOneBuffer[0] == '-')
{
numberOneBuffer = numberOneBuffer.Substring(1, numberOneBuffer.Length - 1);
}
else
{
numberOneBuffer = "-" + numberOneBuffer;
}
}
else
{
numberOneBuffer = "-" + numberOneBuffer;
}
textBox1.Text = Convert.ToString(numberOneBuffer);
}
- private void clearentry_Click(object sender, RoutedEventArgs e)
+ private void clearentry_Click(MouseEventArgs e)
{
numberOneBuffer = "";
textBox1.Text = defaultString;
}
- private void clear_Click(object sender, RoutedEventArgs e)
+ private void clear_Click(MouseEventArgs e)
{
numberOneBuffer = "";
c.reset();
textBox1.Text = defaultString;
}
- private void add_Click(object sender, RoutedEventArgs e)
+ private void add_Click(MouseEventArgs e)
{
if ((numberOneBuffer.Length > 0) && (numberTwoBuffer.Length > 0))
{
string answer = c.operate(Convert.ToDouble(numberTwoBuffer), Convert.ToDouble(numberOneBuffer));
textBox1.Text = answer;
c.setOperation(Calc.Operators.Addition);
}
else
{
c.setOperation(Calc.Operators.Addition);
numberTwoBuffer = numberOneBuffer;
numberOneBuffer = string.Empty;
}
}
- private void minus_Click(object sender, RoutedEventArgs e)
+ private void minus_Click(MouseEventArgs e)
{
if ((numberOneBuffer.Length > 0) && (numberTwoBuffer.Length > 0))
{
textBox1.Text = c.operate(Convert.ToDouble(numberTwoBuffer), Convert.ToDouble(numberOneBuffer));
}
c.setOperation(Calc.Operators.Subtraction);
numberTwoBuffer = numberOneBuffer;
numberOneBuffer = string.Empty;
}
- private void multiple_Click(object sender, RoutedEventArgs e)
+ private void multiple_Click(MouseEventArgs e)
{
if ((numberOneBuffer.Length > 0) && (numberTwoBuffer.Length > 0))
{
textBox1.Text = c.operate(Convert.ToDouble(numberTwoBuffer), Convert.ToDouble(numberOneBuffer));
}
c.setOperation(Calc.Operators.Multiplication);
numberTwoBuffer = numberOneBuffer;
numberOneBuffer = string.Empty;
}
- private void divide_Click(object sender, RoutedEventArgs e)
+ private void divide_Click(MouseEventArgs e)
{
if ((numberOneBuffer.Length > 0) && (numberTwoBuffer.Length > 0))
{
textBox1.Text = c.operate(Convert.ToDouble(numberTwoBuffer), Convert.ToDouble(numberOneBuffer));
}
c.setOperation(Calc.Operators.Division);
numberTwoBuffer = numberOneBuffer;
numberOneBuffer = string.Empty;
}
private string checkDecimal(string s)
{
if (s[s.Length - 1] == '.')
{
s += "0";
}
return s;
}
```