返回 方案一: 基于
方案二:
MudBlazor MudText 组件回车键处理:两种方案详解
javascript
2025-01-06 00:18:17
MudText 组件回车键处理方案
在开发自定义文本编辑器时,常常需要对回车键进行特殊处理。当使用 MudBlazor 的 MudText
组件时,如果想在用户按下回车键后根据当前行内容执行不同操作,例如:内容不为空时创建新行,为空时弹出菜单,就需要自定义事件逻辑。下面我们探讨一些具体的实现方案。
方案一: 基于 KeyDown
事件的逻辑控制
MudTextField
组件提供了 OnKeyDown
事件,可以捕获用户的键盘输入。通过判断按键和当前行内容,我们可以实现差异化的处理。
实现思路
- 捕获回车键: 在
OnKeyDown
事件中,检查e.Key
是否为 “Enter”。 - 获取当前行内容: 需要通过某种方式获取光标所在行的内容,这可能涉及文本区域的内容和光标位置计算,
MudText
本身没有提供直接的接口,可能需要用javascript来辅助获取当前行。 - 根据内容执行操作:
- 如果当前行有文本,允许组件的默认行为,即插入换行符。
- 如果当前行为空,则设置
_preventDefault
为true
以阻止插入换行,同时弹出菜单。
代码示例 (简化)
@using Microsoft.AspNetCore.Components.Web;
@inject IJSRuntime JSRuntime
<MudTextField T="string" Label="Multiline" Variant="Variant.Text" Text="@sampleText" Lines="50" AutoGrow="true" Immediate="true" OnKeyDown="@HandleKeyDown" InputId="editor"/>
<div id="menuContainer" style="display: @(_showMenu ? "block" : "none"); position:absolute;">
<MudMenu>
<MudMenuItem>Option 1</MudMenuItem>
<MudMenuItem>Option 2</MudMenuItem>
</MudMenu>
</div>
@code {
string sampleText = "wwswswLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
bool _preventDefault;
bool _showMenu = false;
double _menuTop,_menuLeft;
protected async override Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JSRuntime.InvokeVoidAsync("addClickToHide", "menuContainer",DotNetObjectReference.Create(this));
}
}
[JSInvokable]
public void CloseMenu(){
_showMenu = false;
StateHasChanged();
}
protected async Task HandleKeyDown(KeyboardEventArgs e)
{
if (e.Key == "Enter")
{
var pos = await JSRuntime.InvokeAsync<dynamic>("getCurrentCursorPosition", "editor");
var textLines=sampleText.Split('\n');
var rowCount = pos.row;
if(rowCount>=textLines.Length){
rowCount=textLines.Length-1;
}
if (textLines[rowCount].Trim().Length>0)
{
_preventDefault = false;
}
else
{
_preventDefault = true;
_menuTop=pos.y;
_menuLeft = pos.x;
_showMenu=true;
StateHasChanged();
await JSRuntime.InvokeVoidAsync("setPosition", "menuContainer",_menuTop,_menuLeft);
}
}else {
_preventDefault=false;
}
}
}
javascript 函数:
function getCurrentCursorPosition(inputId) {
var textarea = document.getElementById(inputId);
var rowCount = 0, start = 0 , y=0,x=0;
if(textarea){
start= textarea.selectionStart;
let text=textarea.value;
var subText = text.substring(0,start)
y = textarea.getBoundingClientRect().y +textarea.offsetHeight;
x = textarea.getBoundingClientRect().x ;
rowCount = subText.split('\n').length-1
}
return { row:rowCount,x:x,y:y }
}
function setPosition(containerId, top, left){
const menu =document.getElementById(containerId)
menu.style.top =`${top}px`;
menu.style.left=`${left}px`;
}
function addClickToHide(containerId, instance){
document.addEventListener('click', (event)=>{
const menu= document.getElementById(containerId)
if(menu){
if(!menu.contains(event.target)){
instance.invokeMethodAsync('CloseMenu')
}
}
})
}
操作步骤:
- 添加一个 MudTextField 组件并设置相关的属性。
- 实现
HandleKeyDown
事件。 - 通过js 获取鼠标当前行及位置信息,判断是否显示菜单
- 如果需要则调用
StateHasChanged
更新UI。
注意事项:
- 需要仔细计算光标所在行的内容。文本区域的多行输入与单行输入有所区别,并且文本内容本身在持续变化,对性能要求较高时可能要考虑防抖节流。
- js 相关方法依赖 DOM ,确保js在浏览器端已经初始化
方案二: @bind-Value
的数据驱动处理
可以使用双向绑定的 @bind-Value
,监测输入数据的变化,然后在 oninput
或者失去焦点时进行检测。该方案相较于方案一,更容易理解,在复杂场景下控制力更强,并且不依赖Javascript,性能更好,并且可以支持@ref
来做其他处理,可以用来扩展更多的文本框控制操作。
实现思路
- 使用
@bind-Value
将文本内容与一个变量绑定。 - 实现
oninput
事件, 获取事件e.Value
以及 上一个绑定的变量,通过分析两个字符串来判断是否回车 - 根据文本内容判断执行相应逻辑
代码示例
<MudTextField T="string" Label="Multiline" Variant="Variant.Text" @bind-Value="_textContent" Lines="50" AutoGrow="true" Immediate="true" oninput="@HandleInput" @ref="@_mudTextRef" />
<div id="menuContainer" style="display: @(_showMenu ? "block" : "none"); position:absolute; background:wheat">
<MudMenu>
<MudMenuItem>Option 1</MudMenuItem>
<MudMenuItem>Option 2</MudMenuItem>
</MudMenu>
</div>
@code{
string _textContent = "wwswswLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
string? _previousText = null;
bool _showMenu=false;
double _menuTop,_menuLeft;
MudTextField<string> _mudTextRef;
protected override void OnInitialized()
{
_previousText = _textContent;
}
protected void HandleInput(ChangeEventArgs e){
var text =(string)e.Value;
if ( text != null &&_previousText != null )
{
var index= text.IndexOf( "\n", StringComparison.Ordinal);
if ( index!=-1&& text.Length==_previousText.Length+1){
var lines=_previousText.Split("\n");
var rowIndex = text.Substring(0,index).Split("\n").Length;
if(rowIndex>lines.Length){
rowIndex = lines.Length;
}
var content= lines[rowIndex-1];
if(string.IsNullOrWhiteSpace(content)){
// 避免重新渲染时,将内容添加到input
var newValue = _previousText.Substring(0,_previousText.Length-1);
_mudTextRef.Text = newValue;
_textContent=newValue;
_previousText=_textContent;
_showMenu=true;
StateHasChanged();
var textRect= _mudTextRef.Element.BoundingClientRect;
_menuLeft = textRect.X ;
_menuTop=textRect.Y+textRect.Height;
//TODO JS Set positon
} else{
_textContent=text;
_previousText =text;
}
return;
}
_textContent=text;
_previousText =text;
}
_showMenu=false;
StateHasChanged();
}
}
操作步骤:
- 将
MudTextField
的@bind-Value
与字符串变量绑定。 - 实现 oninput 方法来判断是否回车。
- 设置
MudTextField
的@ref
,并根据位置判断是否弹出菜单。
注意事项:
- 该方案性能更好,完全由 c# 代码实现,没有
JSRuntime
的额外调用,更可靠。
总结
上面介绍了处理 MudText 组件回车键的两种方法。实际应用中可根据需要选用合适的方案。第一种方案更贴近初始的需求,在细节方面更精确,可以控制preventDefault 的动作,第二种方案理解更容易,更推荐使用第二种。
注意考虑组件的重绘及性能损耗,在需求复杂的时候尽量使用状态驱动。另外当需求增加例如支持自定义字符,自动补全,关键词,错误标记等需求时候第二种方式的可维护性更高,方便进行扩展,并且配合js 来达到某些特定效果。