Note
The WinForms CodeEditor control is available as part of this example. It is not included in the DevExpress WinForms UI distribution.
This example wraps the open source Monaco Editor (v0.55.1). The editor is hosted inside Microsoft WebView2 and exposed through a reusable CodeEditor WinForms control.
- .NET 8+
- Microsoft WebView2 Runtime
- Windows
The CodeEditor control exposes properties that map directly to Monaco Editor settings and can be configured at runtime.
Features include:
- 50+ Built-in Languages
- DevExpress Skin Integration
- Theme Token Rule Editor
- Configurable Editor Options (line numbers, minimap, folding, word wrap, and more)
- Track Changes
- Read-Only Mode
- Load/Save Files
Text— Specifies editor contentReadOnly— Enables read-only modeIsModified— Indicates whether unsaved changes exist
codeEditor.Text = "public class Demo { }";
codeEditor.ReadOnly = false;
bool isModified = codeEditor.IsModified;The CodeEditor control supports 50+ built-in languages (C#, JavaScript, Python, SQL, XML, JSON, and more) and allows you to register custom programming languages as needs dictate.
// Specify a programming language using a Monaco language identifier.
// For example, csharp, javascript, python, sql, json, etc.
codeEditor.EditorLanguage = "csharp";Use the RegisterLanguage method to add a new language definition:
codeEditor.RegisterLanguage(new LanguageDescriptor {
Id = "myLang", // Unique language identifier
Monarch = "...", // Tokenizer definition
Configuration = "..." // (optional) Language configuration
});The Monarch property must contain a valid Monaco Monarch tokenizer definition provided as a JavaScript object literal.
Example:
var language = new LanguageDescriptor {
Id = "simpleLang",
Monarch = @"
{
tokenizer: {
root: [
[/[a-z_$][\w$]*/, 'identifier'],
[/\d+/, 'number'],
[/"".*?""/, 'string']
]
}
}",
Configuration = @"
{
comments: {
lineComment: '//'
}
}"
};
codeEditor.RegisterLanguage(language);codeEditor.ShowLineNumbers = true;
codeEditor.ShowMinimap = true;
codeEditor.ShowGlyphMargin = false;
codeEditor.ApplyDevExpressColors = true;codeEditor.EnableSmoothScrolling = true;
codeEditor.EnableScrollBeyondLastLine = true;
codeEditor.ScrollBeyondLastColumn = 5;
codeEditor.EnableStickyScroll = true;
codeEditor.LineNumbersMinChars = 5;
codeEditor.TabSize = 4;codeEditor.InsertSpaces = true;
codeEditor.DetectIndentation = true;
codeEditor.AutoIndent = EditorAutoIndent.Full;
codeEditor.WordWrap = EditorWordWrap.On;
// IntelliSense and suggestions
codeEditor.EnableQuickSuggestions = true;
codeEditor.EnableWordBasedSuggestions = true;
codeEditor.EnableSuggestOnTriggerCharacters = true;
codeEditor.EnableParameterHints = true;- Context Menu
- Drag and Drop
- Zoom (
Ctrl+ mouse wheel)
codeEditor.EnableContextMenu = true;
codeEditor.EnableDragAndDrop = true;
codeEditor.EnableMouseWheelZoom = false;The CodeEditor control hosts the Monaco Editor inside a WebView2 instance and exposes it as a reusable WinForms component.
public class CodeEditor : XtraUserControl, IEditorMessageHandler {
WebView2? webView;
IEditorCommandChannel commandChannel = null!;
EditorMessageDispatcher messageDispatcher = null!;
public CodeEditor() {
webView = new WebView2 {
Dock = DockStyle.Fill
};
commandChannel = new WebView2CommandChannel(webView);
messageDispatcher = new EditorMessageDispatcher(this);
Controls.Add(webView);
LookAndFeel.StyleChanged += LookAndFeel_StyleChanged;
Rules = new List<MonacoThemeRule>();
}
}All interaction between C# and Monaco is handled via JSON messages over WebView2.
public void Send(EditorCommandType type, object? payload = null) {
if(!IsReady)
return;
var cmd = new EditorCommand {
Type = type,
Payload = payload
};
var options = new JsonSerializerOptions(JsonSerializerOptions.Web);
string json = JsonSerializer.Serialize(cmd, options);
webView?.CoreWebView2?.PostWebMessageAsJson(json);
}Incoming messages:
public void HandleWebMessageReceived(object? sender, CoreWebView2WebMessageReceivedEventArgs e) {
if(sender is not CoreWebView2)
return;
EditorMessage? message;
try {
message = JsonSerializer.Deserialize<EditorMessage>(e.WebMessageAsJson, JsonSerializerOptions.Web);
}
catch { return; }
if(message?.Type == null)
return;
switch(message.Type) {
case EditorMessageType.TextChanged:
handler.OnTextChanged(message.Payload.GetString() ?? string.Empty);
break;
case EditorMessageType.EditorReady:
handler.OnEditorReady();
break;
case EditorMessageType.IsDirtyChanged:
handler.OnIsModifiedChanged(message.Payload.GetBoolean());
break;
case EditorMessageType.Languages:
List<string> langs = message.Payload
.EnumerateArray()
.Select(x => x.GetString()!)
.ToList();
handler.OnLanguagesReceived(langs);
break;
}
}All properties are restored after initialization:
void ApplyCurrentState() {
RestoreRegisteredLanguages();
SetEditorLanguage(EditorLanguage);
SetWordWrap(WordWrap);
SetTheme(ThemeName);
SetAutoIndent(AutoIndent);
commandChannel.Send(EditorCommandType.SetReadOnly, ReadOnly);
commandChannel.Send(EditorCommandType.SetText, Text);
EditorOptionHelper.SendOption(commandChannel, EditorOption.LineNumbers, ShowLineNumbers);
EditorOptionHelper.SendOption(commandChannel, EditorOption.Minimap, ShowMinimap);
EditorOptionHelper.SendOption(commandChannel, EditorOption.Folding, EnableFolding);
EditorOptionHelper.SendOption(commandChannel, EditorOption.TabSize, TabSize);
EditorOptionHelper.SendOption(commandChannel, EditorOption.InsertSpaces, InsertSpaces);
//...
}CodeEditor options are exposed as public properties:
//codeEditor.ShowLineNumbers = true;
public bool ShowLineNumbers {
get => showLineNumbers;
set {
if(showLineNumbers == value) return;
showLineNumbers = value;
EditorOptionHelper.SendOption(commandChannel, EditorOption.LineNumbers, value);
}
}The CodeEditor supports both built-in and custom languages.
public void RegisterLanguage(LanguageDescriptor language) {
if(language == null)
throw new ArgumentNullException(nameof(language));
var payload = new {
id = language.Id,
monarch = language.Monarch,
configuration = language.Configuration
};
commandChannel.Send(EditorCommandType.RegisterLanguage, payload);
registeredLanguages[language.Id] = language;
}The editor tracks unsaved changes:
codeEditor.IsModifiedChanged += (s, e) => {
saveItem.Enabled = codeEditor.IsModified;
};(you will be redirected to DevExpress.com to submit your response)
