A quick example of using Blazor with WPF

Next we will explain how to use Blazor in WPF and use Blazor to do some file editing operations. Here are the things you need to use

  • WPF

  • Blazor

  • Masa Blazor

  • Monaco

Install Masa Blazor template

Use the CMD command to install the template

dotnet new install MASA.Template

New Masa Blazor WPF App

  1. Find the template as shown in the picture and click Next

f775fe35522c57e9731bf2bd1285e8c0.png

  1. Next, create a new project name FileEditor

e83da6d929c47bd88cc2636a9289afe1.png

Add Monaco

  1. Open wwwroot/index.html, and reference Monaco’s dependencies, and add the following dependencies to the end of the body.

<script>
    var require = { paths: { 'vs': 'https://cdn.masastack.com/npm/monaco-editor/0.34.1/min/vs' } };
</script>
<script src="//i2.wp.com/cdn.masastack.com/npm/monaco-editor/0.34.1/min/vs/loader.js"></script>
<script src="//i2.wp.com/cdn.masastack.com/npm/monaco-editor/0.34.1/min/vs/editor/editor.main.nls.js"></script>
<script src="//i2.wp.com/cdn.masastack.com/npm/monaco-editor/0.34.1/min/vs/editor/editor.main.js"></script></ pre>
 <ol><li><p>Create a new <code>Pages/Index.razor.cs</code> file</p></li></ol>
 <pre>using System.IO;
using System.Text;
using Masa.Blazor;
using Masa.Blazor.Presets;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;

namespace FileEditor.Pages;

public partial class Index : IDisposable
{
    /// <summary>
    /// Text content
    /// </summary>
    private string value;

    private MMonacoEditor _editor;

    private DotNetObjectReference<Index>? _objRef;

    /// <summary>
    /// Define the initial configuration of Monaco
    /// </summary>
    private object options = new
    {
        language = "md", // Set syntax
        automaticLayout = true, // Height adaptive
        theme = "vs-dark", // theme
    };

    private string fullName;

    protected override void OnInitialized()
    {
        _objRef = DotNetObjectReference.Create(this);
    }

    /// <summary>
    ///Specific file path
    /// </summary>
    [Parameter]
    [CascadingParameter(Name = nameof(FullName))]
    public string FullName
    {
        get => fullName;
        set
        {
            fullName = value;
            UpdateValue();
        }
    }

    /// <summary>
    /// Monaco initialization event
    /// </summary>
    private async Task InitMonaco()
    {
        // Monitor CTRL + S 2097 = CTRL + S shortcut key
        // Call Monaco's Command, pass the current object, and specify to call the specified method of the signed object when the shortcut key is triggered.
        await _editor.AddCommandAsync(2097, _objRef, nameof(SaveValue));
    }

    /// <summary>
    /// Update value
    /// </summary>
    private void UpdateValue()
    {
        if (string.IsNullOrEmpty(fullName))
        {
            return;
        }

        var info = new FileInfo(fullName);
        if (!info.Exists) return;
        using var fileStream = info.OpenText();
        value = fileStream.ReadToEnd();
    }

    /// <summary>
    /// Update file content
    /// </summary>
    [JSInvokable]
    public async Task SaveValue()
    {
        try
        {
            await using var fileStream = File.OpenWrite(fullName);
            fileStream.Position = 0;
            await fileStream.WriteAsync(Encoding.UTF8.GetBytes(value));
            fileStream.Close();
        }
        catch (Exception e)
        {
            await PopupService.EnqueueSnackbarAsync(new SnackbarOptions()
            {
                Title = "Error saving file",
                Content = e.Message
            });
        }
    }

    public void Dispose()
    {
        _editor.Dispose();
        _objRef?.Dispose();
    }
}

In the Index.razor.cs file, we implement the set that intercepts FullName. When it is set, it means that the superior component has selected the file and passed the parameters to the current component through CascadingParameter .

And update the current Value,

Open Index.razor

@page "/"
@inject IPopupService PopupService

<MMonacoEditor InitCompleteHandle="async () => await InitMonaco()"
               @bind-Value="value"
               Height="@("100%")"
               EditorOptions="options" @ref="_editor">
</MMonacoEditor>

We have bound some methods and parameters of cs, and bind-value the value of value. We have updated the valuecs file. >The displayed value of the UI is automatically updated.

Then we open the Shared/MainLayout.razor file and add the open file selector to select the file.

@using Microsoft.Win32
@inherits LayoutComponentBase

<MApp>
    <MAppBar App>
        <MAppBarNavIcon @onclick="() => _drawer = !_drawer"></MAppBarNavIcon>
        <MToolbarTitle>FileEditor</MToolbarTitle>
        <MButton OnClick="OpenFile">Open file</MButton>
        <MSpacer></MSpacer>
        <MButton Text Color="primary" Target="_blank" Href="https://docs.masastack.com/blazor/introduction/why-masa-blazor">About</MButton>
    </MAppBar>

    <MNavigationDrawer App @bind-Value="_drawer">
        <MList Nav Routable>
            <MListItem Href="/" ActiveClass="primary--text">
                <MListItemIcon>
                    <MIcon>mdi-home</MIcon>
                </MListItemIcon>
                <MListItemContent>
                    <MListItemTitle>Home</MListItemTitle>
                </MListItemContent>
            </MListItem>
            <MListItem Href="/counter" ActiveClass="primary--text">
                <MListItemIcon>
                    <MIcon>mdi-plus</MIcon>
                </MListItemIcon>
                <MListItemContent>
                    <MListItemTitle>Counter</MListItemTitle>
                </MListItemContent>
            </MListItem>
            <MListItem Href="/fetchdata" ActiveClass="primary--text">
                <MListItemIcon>
                    <MIcon>mdi-list-box</MIcon>
                </MListItemIcon>
                <MListItemContent>
                    <MListItemTitle>Fetch data</MListItemTitle>
                </MListItemContent>
            </MListItem>
        </MList>
    </MNavigationDrawer>

    <MMain>
        <MContainer Fluid Style="height: 100%">
            <CascadingValue Value="fullName" Name="FullName">
                <MErrorHandler>
                    @Body
                </MErrorHandler>
            </CascadingValue>
        </MContainer>
    </MMain>
</MApp>

@code {

    private bool? _drawer;

    private string fullName;

    private void OpenFile()
    {
        var openFileDialog = new OpenFileDialog();
        openFileDialog.Title = "Please select your file";
        openFileDialog.Filter = "Text file (*.txt, *.md)|*.txt;*.md";
        bool? result = openFileDialog.ShowDialog();
        if (result == true)
        {
            fullName = openFileDialog.FileName;
        }
    }
}

Here we will use Microsoft.Win32.OpenFileDialog to open the file selector and specify the type of selected file.

If the current file selector returns true, the value of fullName and fullName will be passed to through the binding of the CascadingValue component. All subcomponents within .

Let’s take a look at the actual use effect.

5dddb7c8a61b41dd23b8c41decda9be4.gif