英文:
Subscribe to an EventCallback from a Layout when using Router
问题
具备Blazor WebAssembly的基本知识,但不是特别高级。
我熟悉从子组件使用EventCallback。但我在文档中没有找到如何订阅从Blazors路由器渲染的页面的EventCallback的方法。
使用案例是,我有一个具有@Main占位符的布局页面。我想订阅特定页面的EventCallback,但无法直接引用该页面的EventCallback,因为@Main可以是任何页面,而且没有类类型。
具体来说,假设我有几个页面允许用户进行选择。进一步假设它们都共享相同的布局页面。请参考以下布局页面代码。我有一个级联参数,用于在所有页面和组件之间共享选择。
如何订阅从布局页面插入到@Main中的页面的EventCallbacks?
<main>
<CascadingValue Value="@selectionIds" Name="selectionIds">
@Body
<DetailView />
</CascadingValue>
</main>
请注意,以上是对您提出的问题的翻译。如果您需要进一步的帮助或解释,请随时提出。
英文:
Have working knowledge of Blazor WebAssembly but not too advanced.
I'm good with EventCallback from child components. What I haven't seen anywhere documented is how to subscribe to EventCallback from pages rendered from Blazors Router.
The use case is i've got a layout page with the placeholder for @Main. I'd like to subscribe to an EventCallback for a certain page within the layout but there's no way to reference that page's EventCallback directly since @Main could be any page and also it has no class type.
Specifically lets say i have several pages that allow a user to make a selection. Further assume they all share the same layout page. See the code below for the layout page code. I've got a cascading parameter to share the selections across all the pages and components.
How would I subscribe to the EventCallbacks in the pages that would be inserted into @Main from the layout page?
<main>
<CascadingValue Value="@selectionIds" Name="selectionIds" >
@Body
<DetailView />
</CascadingValue>
</main>
答案1
得分: 1
以下是翻译好的内容:
这是一个使用Scoped
服务来维护数据的Blazor通知模式实现。我使用了一个简单的国家选择示例来演示原理。在上下文之外,selectionIds
意义不大。
第一步是从组件中分离数据和状态:
public class CountryData
{
public string? Country { get; private set; }
public event EventHandler? CountryChanged;
public void SetCountry(string? value )
{
this.Country = value;
this.CountryChanged?.Invoke( this, EventArgs.Empty );
}
}
注册为Scoped服务:
builder.Services.AddScoped<CountryData>();
一个CountryDisplay
组件来显示国家。
@implements IDisposable
@inject CountryData CountryData
<div class="alert alert-primary m-2">@this.CountryData.Country</div>
@code {
protected override void OnInitialized()
=> this.CountryData.CountryChanged += OnCountryChanged;
private void OnCountryChanged(object? sender, EventArgs e)
=> this.StateHasChanged();
public void Dispose()
=> this.CountryData.CountryChanged += OnCountryChanged;
}
还有一个CountrySelector
组件来修改它:
@inject CountryData CountryData
<div class="bg-light m-2 pt-2 p-4 border border-1">
<h3>Country Selector</h3>
<InputSelect class="form-select" @bind-Value:get="this.CountryData.Country" @bind-Value:set="this.CountryData.SetCountry">
@if (this.CountryData.Country is null)
{
<option selected disabled value=""> -- 选择一个国家 -- </option>
}
@foreach (var country in CountryProvider.CountryList)
{
<option value="@country">@country</option>
}
</InputSelect>
</div>
@code {
// 注意 - @bind-Value:get 可能会报错。这是一个SDK问题。代码将会编译。
}
CountryLayout
@inherits LayoutComponentBase
<PageTitle>SO76864163</PageTitle>
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<div class="top-row px-4">
<a href="https://docs.microsoft.com/aspnet/" target="_blank">关于</a>
</div>
<article class="content px-4">
<CountryDisplay />
@Body
</article>
</main>
</div>
@code {
private CountryData _countryData = new();
}
以及Index
:
@page "/"
@layout CountryLayout
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<SurveyPrompt Title="Blazor对您的工作有何帮助?" />
<CountrySelector />
和Counter
:
@page "/counter"
@layout CountryLayout
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">当前计数:@currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">点击我</button>
<CountrySelector />
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
英文:
Here's a Blazor Notification Pattern implementation that uses a Scoped
service to maintain the data. I've used a simple country select to demonstrate the principles. selectionIds
doesn't mean a lot out of context.
The first step is to separate the data and state from the component:
public class CountryData
{
public string? Country { get; private set; }
public event EventHandler? CountryChanged;
public void SetCountry(string? value )
{
this.Country = value;
this.CountryChanged?.Invoke( this, EventArgs.Empty );
}
}
Registered as a Scoped Service
builder.Services.AddScoped<CountryData>();
A CountryDisplay
component to dispay the country.
@implements IDisposable
@inject CountryData CountryData
<div class="alert alert-primary m-2">@this.CountryData.Country</div>
@code {
protected override void OnInitialized()
=> this.CountryData.CountryChanged += OnCountryChanged;
private void OnCountryChanged(object? sender, EventArgs e)
=> this.StateHasChanged();
public void Dispose()
=> this.CountryData.CountryChanged += OnCountryChanged;
}
And a CountrySelector
component to modify it:
@inject CountryData CountryData
<div class="bg-light m-2 pt-2 p-4 border border-1">
<h3>Country Selector</h3>
<InputSelect class="form-select" @bind-Value:get="this.CountryData.Country" @bind-Value:set="this.CountryData.SetCountry">
@if (this.CountryData.Country is null)
{
<option selected disabled value=""> -- Select a Country -- </option>
}
@foreach (var country in CountryProvider.CountryList)
{
<option value="@country">@country</option>
}
</InputSelect>
</div>
@code {
// NOTE - @bind-Value:get may give you an error. It's a SDK issue. The code will compile.
}
CountryLayout
@inherits LayoutComponentBase
<PageTitle>SO76864163</PageTitle>
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<div class="top-row px-4">
<a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
</div>
<article class="content px-4">
<CountryDisplay />
@Body
</article>
</main>
</div>
@code {
private CountryData _countryData = new();
}
And Index
:
@page "/"
@layout CountryLayout
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<SurveyPrompt Title="How is Blazor working for you?" />
<CountrySelector />
And Counter
:
@page "/counter"
@layout CountryLayout
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
<CountrySelector />
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
答案2
得分: -2
我进行了更多的研究,对于似乎是一个常见用例来说,这似乎是文档不足的。下面的修改有效。
显然,EventCallback可以像任何其他属性一样通过CascadingParameter传递。因此,您只需将@Main路由占位符包装在另一个用于EventCallback的CascadingParameter中。
<main>
<CascadingValue Value="@selectionIds" Name="selectionIds">
<CascadingValue Value="@SelectionChanged" Name="SelectedChanged">
@Body
<DetailView />
</CascadingValue>
</CascadingValue>
</main>
@code{
EventCallback<string> SelectionChanged =>
EventCallback.Factory.Create<string>(this, (ids) =>
Console.WriteLine($"Selection of {ids} Bubbled Up.")
);
}
然后在任何路由传递的页面和/或其他组件中:
[CascadingParameter(Name = "SelectionChanged")]
public EventCallback<string> SelectionChanged { get; set; }
private async Task someTrigger(){
await SelectionChanged.InvokeAsync(selectionIds);
}
请注意,上述代码是为了说明目的而提供的,并不包含完整的上下文信息。
英文:
I did considerable more research and for what seems like a common use case this feels far too under documented. The below modifications work.
It wasn't obvious that an EventCallback can be passed just like any other property via CascadingParameter. So you simply need to wrap the @Main route placeholder with an another CascadingParameter for the EventCallback.
<main>
<CascadingValue Value="@selectionIds" Name="selectionIds" >
<CascadingValue Value="@SelectionChanged" Name="SelectedChanged" >
@Body
<DetailView />
</CascadingValue>
</CascadingValue>
</main>
@code{
EventCallback<string> SelectionChanged =>
EventCallback.Factory.Create<string>(this, (ids) =>
Console.WriteLine($"Selection of {ids} Bubbled Up.")
);
}
Then in any of the router delivered pages and/or other components:
[CascadingParameter(Name = "SelectionChanged")]
public EventCallback<string> SelectionChanged { get; set; }
private async Task someTrigger(){
await SelectionChanged.InvokeAsync(selectionIds);
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论