Элемент ввода не отображается повторно, вариант использования заключается в том, что когда пользователь вводит некоторый текст, текущая дата заполняется в элемент ввода через атрибут «value
». См. код ниже.
@page "/"
<label>Text Binding check:</label>
<input type = "text" value = "@TextData" @onchange = "OnChange_Text" />
<br />
<br />
<label>Text Dummy:</label>
<input type = "text" value = "dummy" />
@code
{
public string? TextData { get; set; } = "12-12-2022";
protected override bool ShouldRender()
{
return true;
}
private void OnChange_Text(ChangeEventArgs e)
{
TextData = GetValue(e.Value?.ToString());
InvokeAsync(() => StateHasChanged());
}
private string GetValue(string? strValue)
{
return strValue switch
{
"test" => DateTime.Now.ToString("dd-MM-yyyy"),
"data" => DateTime.Now.ToString("dd-MM-yyyy"),
"nash" => DateTime.Now.ToString("dd-MM-yyyy"),
_ => DateTime.Now.ToString("dd-MM-yyyy")
};
}
}
Если пользователь вводит тот же текст или любой текст еще раз, дата не отображается в элементе ввода во второй раз. Даже если для StateHasChanged()
и ShouldRender
установлено значение true, значение не отображается.
Таким образом, согласно механизму Blazor, элемент на самом деле не изменяется, поэтому дерево рендеринга не будет отображать входной элемент в DOM. Как я могу решить эту проблему и как я могу повторно отобразить элемент, который не изменился? Я подумал SateHasChanged()
и ShouldRender
обновить пользовательский интерфейс, разница в элементах его не волнует.
Возможно, это связано с оптимизацией рендеринга компонентов Blazor. У меня был тест с вашими кодами, и я также обнаружил, что исходное значение для TextData
равно 12-12-2022
, а затем, когда мы вводим data
в поле ввода, TextData
будет изменено на DataTime.Now
, что равно 26-08-2024
. В следующий раз, когда мы изменим входное значение, оно будет изменено на 26-08-2024
, что на самом деле не меняет значение. Тогда, возможно, из-за некоторой оптимизации, даже если мы вызовем StateHasChanged()
, страница не будет перерисовываться.
Мой обходной путь — использовать JS для прямого обновления DOM.
<input type = "text" id = "testInput" value = "@TextData" @onchange = "OnChange_Text" />
private void OnChange_Text(ChangeEventArgs e)
{
TextData = GetValue(e.Value?.ToString());
JS.InvokeVoidAsync("setInputValue", "testInput", TextData);
}
И поместите js ниже сразу за <script src = "_framework/blazor.server.js"></script>
в _Layout.cshtml
, так как ваш тег — сервер blazor. Если у вас есть веб-приложение blazor, то это должно быть <script src = "_framework/blazor.web.js"></script>
, которое есть в App.razor
.
<script>
window.setInputValue = (elementId, value) => {
//alert(elementId);//for test purpose
//alert(value);
document.getElementById(elementId).value = value;
};
</script>
Да, я просто тестирую серверное приложение blazor и нахожу этот обходной путь, чтобы лучше понять, предпочитает ли кто-то еще метод JS. Спасибо за то, что поделились!
В тот момент, когда вы начинаете изменять разделы DOM, контролируемые компонентами Blazor, реальный DOM и виртуальный DOM Blazor рассинхронизируются, и вы получаете всевозможные неожиданные результаты.
Лично я бы избегал использования JS при работе с Blazor, если только вы действительно не можете найти альтернативное или жизнеспособное решение. Blazor уже предоставляет все, чтобы мы могли продолжать использовать C#.
Прежде всего, вам понадобится интерактивность для вашей страницы, иначе изменения не будут отображены (по крайней мере, для новых версий .NET и blazor). Это можно активировать с помощью следующей строки @rendermode InteractiveServer
.
Почему бы вам не использовать стандартный компонент InputText
? Здесь вы можете привязать значение к своему свойству и обновить его напрямую. Это также позволит Blazor узнать, что что-то изменилось. StateHasChanged следует вызывать только в случае необходимости, поскольку Blazor знает, когда произошли изменения (другая история с дочерними компонентами).
@page "/"
@rendermode InteractiveServer
<PageTitle>SO Test</PageTitle>
<label>Text Binding check:</label>
<InputText @bind-Value = "@TextData" />
<br />
<br />
<label>Text Dummy:</label>
<input type = "text" value = "dummy" />
@code
{
private string? _text;
private string? TextData
{
get => _text ?? "12-12-2022";
set
{
_text = value switch
{
"test" => DateTime.Now.ToString("dd-MM-yyyy"),
"data" => DateTime.Now.ToString("dd-MM-yyyy"),
"nash" => DateTime.Now.ToString("dd-MM-yyyy"),
_ => DateTime.Now.ToString("dd-MM-yyyy")
};
}
}
}
Если вы не хотите иметь логику непосредственно в свойстве, то вы также можете передать ее методу и использовать соответствующие атрибуты в компоненте InputText (но, на мой взгляд, это выглядит немного менее привлекательно).
// removed for brevity
<label>Text Binding check:</label>
<InputText id = "name" Value = "@TextData" ValueChanged = "HandleValueChange" ValueExpression = "() => TextData"></InputText>
<br />
// removed for brevity
@code
{
private string? TextData { get; set; } = "12-12-2022";
private void HandleValueChange(string? obj)
{
TextData = obj switch
{
"test" => DateTime.Now.ToString("dd-MM-yyyy"),
"data" => DateTime.Now.ToString("dd-MM-yyyy"),
"nash" => DateTime.Now.ToString("dd-MM-yyyy"),
_ => DateTime.Now.ToString("dd-MM-yyyy")
};
}
}
Я предполагаю, что это интерактивная страница.
Используйте @bind
и @bind:after
. Пусть привязка и обработчик событий пользовательского интерфейса сделают всю работу.
Обратный вызов привязки [для установки TextData
] — это событие пользовательского интерфейса. Он устанавливает значение, а затем вызывает зарегистрированный метод after
. Наконец, он вызывает StateHasChanged
для рендеринга нового состояния.
Я изменил полученные даты и форматирование, чтобы было легче видеть разные результаты.
@page "/"
<label>Text Binding check:</label>
<input type = "text" @bind = "TextData" @bind:after = "OnChange_Text" />
<br />
<br />
<label>Text Dummy:</label>
<input type = "text" value = "@EnteredValue" />
@code
{
public string? TextData { get; set; } = "12-Dec-2022";
public string? EnteredValue;
private void OnChange_Text()
{
this.EnteredValue = this.TextData;
this.TextData = GetValue(this.TextData);
}
private string GetValue(string? strValue)
{
return strValue switch
{
"test" => DateTime.Now.AddMonths(2).ToString("dd-MMM-yyyy"),
"data" => DateTime.Now.AddMonths(1).ToString("dd-MMM-yyyy"),
"nash" => DateTime.Now.ToString("dd-MMM-yyyy"),
_ => DateTime.Now.AddMonths(-1).ToString("dd-MMM-yyyy")
};
}
}
он же Шон Спасибо за решение. Но почему мой код не работал так, как ожидалось? Привязка также устанавливает значение свойства в атрибут «значение» и повторно отображает элемент, верно?
private **async** void OnChange_Text(ChangeEventArgs e)
{
TextData = GetValue(e.Value?.ToString());
**await** InvokeAsync(StateHasChanged);
}
Конечно, это не способ достичь той функциональности, на которую вы смотрите. Просто используйте @bind-Value
и @bind-Value:after
Для этого вам не нужно прибегать к JS Interop.