Blazor 创建通用下拉菜单

Blazor creating a generic drop-down

我正在尝试创建一个通用下拉组件以在我们的系统中使用。但是,在更改所选项目时绑定 EventCallback 时遇到问题。

这是我目前对通用下拉菜单的想法:

<div class="inputItem @(SizeClass) dropdown" style="min-width:@(Width);">
    <SfDropDownList TItem="object" TValue="int" Placeholder="Select a category" DataSource="DataSource" Value="@(SelectedItem)" EnableVirtualization="true">
        <DropDownListEvents TItem="object" TValue="int" ValueChange="@OnSelectedItemChanged"></DropDownListEvents>
        <DropDownListFieldSettings Text="@(TextField)" Value="@(ValueField)" />
    </SfDropDownList>
</div>

@code {
    [Parameter]
    public IEnumerable<object> DataSource { get; set; }

    [Parameter]
    public EventCallback<ChangeEventArgs<int, object>> OnSelectedItemChanged { get; set; }

    [Parameter]
    public string Placeholder { get; set; }

    [Parameter]
    public string TextField { get; set; }

    [Parameter]
    public int SelectedItem { get; set; }

    [Parameter]
    public string ValueField { get; set; }

    [Parameter]
    public string Width { get; set; }

    [Parameter]
    public string SizeClass { get; set; }

}

下面是一个调用它的示例组件:

@page "/news/create"
@inject NavigationManager NavManager;
@using Microsoft.EntityFrameworkCore;
@inject IDbContextFactory<FIS2_DbContext> contextFactory;
@inject IFileService fileService;
@using FIS2withSyncfusion.Controls;
@using FIS2withSyncfusion.Models;
@using FIS2withSyncfusion.Utility;
@using Syncfusion.Blazor.RichTextEditor;
@using System.Collections.Generic;
@using System.Threading.Tasks;
@using Newtonsoft.Json;

<div class="dashWrapper">
    <SfDashboardLayout AllowDragging="false" AllowFloating="false" AllowResizing="false" CellAspectRatio="2.5" CellSpacing="@(new double[]{20,20})" Columns="3">
        <DashboardLayoutPanels>
            <DashboardLayoutPanel Column="0" Row="0" SizeX="2" SizeY="2" Id="createNews">
                <HeaderTemplate>
                    <h3>Create A News Item</h3>
                </HeaderTemplate>
                <ContentTemplate>
                    <div class="form-wrapper">
                        <div class="inputRow">
                            <TextBox AutoComplete="@(Syncfusion.Blazor.Inputs.AutoComplete.Off)" Placeholder="Title" Text="@(title)" HTMLAttributes="@textboxValidation" Width="450px" SizeClass="half-width"></TextBox>
                            <DropDownList DataSource="categories" Placeholder="Select a category" SizeClass="half-width" Width="450px" TextField="name" ValueField="id" SelectedItem="@(itemModel.Category)" OnSelectedItemChanged="@(OnSelectedItemChanged)"></DropDownList>
                            @*<SfDropDownList TItem="spGetNewsCategoriesResult" TValue="int" Placeholder="Select a category" @ref="sfDropDown" DataSource="categories" CssClass="inputItem half-width" @bind-Value="@(itemModel.Category)">
                                <DropDownListFieldSettings Text="name" Value="id" />
                                </SfDropDownList>*@
                        </div>
                        <div class="inputRow">
                            <CheckBox Checked="isChecked" Label="Suggest Dates This Should Be Active?" OnCheckChange="@(OnCheckChange)" SizeClass="one-third" Width="300px"></CheckBox>
                            @if (isChecked)
                            {
                                <DateTimePicker Label="Active From:" SelectedDate="@activeFrom" Width="450px" SizeClass="one-third"></DateTimePicker>
                                <DateTimePicker Label="Active To:" SelectedDate="@activeTo" Width="450px" SizeClass="one-third"></DateTimePicker>
                            }
                        </div>
                        <div class="inputRow">
                            <FileUploader MaxSize="@(MaxSize)" OnClearFiles="OnClearFiles" OnFileRemove="OnFileRemove" OnFileUpload="OnFileUpload" SizeClass="full-width" Width="400px"></FileUploader>
                        </div>
                        <RichTextEditor DeniedAttributes="@DeniedAttributes" text=@(itemModel.Content) Height="400px" Width="1600px"></RichTextEditor>
                    </div>
                </ContentTemplate>
            </DashboardLayoutPanel>
        </DashboardLayoutPanels>
    </SfDashboardLayout>
</div>

@if (ShowDialog)
{
    <Dialog Title="Create News Item" message="@Message" OKText="@OKText" cancelText="@CancelText" OnClose="OnDialogClose">
    </Dialog>
}

@code {
    [CascadingParameter]
    Task<AuthenticationState> authenticationStateTask { get; set; }

    public string userName { get; set; }

    private int MaxSize { get; set; }

    private string title { get; set; }
    private int selectedCategory { get; set; }
    private string content { get; set; }
    int count { get; set; }

    private bool ShowDialog { get; set; } = false;
    private string Message { get; set; } = "";
    private string OKText { get; set; } = "";
    private string CancelText { get; set; } = "";

    public DateTime activeTo { get; set; }
    public DateTime activeFrom { get; set; }

    private bool isChecked { get; set; }

    SaveNewsItemModel itemModel = new SaveNewsItemModel();

    List<string> DeniedAttributes = new List<string>() {
        "id", "title", "style"
    };

    Dictionary<string, object> textboxValidation = new Dictionary<string, object>(){
        {"maxlength", "100"}
    };

    List<spGetNewsCategoriesResult> categories = new List<spGetNewsCategoriesResult>();

    private async Task OnCheckChange(bool check)
    {
        isChecked = check;
        StateHasChanged();
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            var authState = await authenticationStateTask;
            var user = authState.User;
            userName = user.Identity.Name;
            var context = contextFactory.CreateDbContext();
            var procedures = context.Procedures;

            categories = await procedures.spGetNewsCategoriesAsync();

            MaxSize = 15 * 1024 * 1024;
        }
    }

    private List<ToolbarItemModel> Tools = new List<ToolbarItemModel>() {
        new ToolbarItemModel()
        {
            Command = ToolbarCommand.Bold
        },
        new ToolbarItemModel()
        {
            Command = ToolbarCommand.Italic
        },
        new ToolbarItemModel()
        {
            Command= ToolbarCommand.Underline
        },
        new ToolbarItemModel()
        {
            Command= ToolbarCommand.Separator
        },
        new ToolbarItemModel()
        {
            Command = ToolbarCommand.Undo
        },
        new ToolbarItemModel()
        {
            Command = ToolbarCommand.Redo
        },
        new ToolbarItemModel()
        {
            Command= ToolbarCommand.Separator
        },
        new ToolbarItemModel()
        {
            Command = ToolbarCommand.OrderedList
        },
        new ToolbarItemModel()
        {
            Command = ToolbarCommand.UnorderedList
        },
        new ToolbarItemModel()
        {
            Command = ToolbarCommand.Separator
        },
        new ToolbarItemModel()
        {
            Command = ToolbarCommand.FontColor
        },
        new ToolbarItemModel()
        {
            Command = ToolbarCommand.CreateLink
        },
        new ToolbarItemModel()
        {
            Command = ToolbarCommand.RemoveLink
        }
    };

    private async Task OnFileUpload(UploadChangeEventArgs args)
    {
        foreach (var file in args.Files)
        {
            var fileName = file.FileInfo.Name;
            using (var ms = file.Stream)
            {
                System.IO.FileInfo fileInfo = new System.IO.FileInfo(fileName);
                int count = 1;
                string tempFileName = fileName;
                while (fileService.TempFileExists(tempFileName))
                {
                    tempFileName = $"({count}) {fileName}";
                    count++;
                }

                var bytes = ms.ToArray();
                await fileService.SaveFileToTempAsync(bytes, tempFileName);
                var mimetype = fileInfo.Extension;
                itemModel.AddFile(fileName, mimetype, tempFileName, contextFactory);
            }
        }
    }

    private async Task OnClearFiles(ClearingEventArgs args)
    {
        foreach (var file in args.FilesData)
        {
            var fileName = file.Name;
            System.IO.FileInfo fileInfo = new System.IO.FileInfo(fileName);
            itemModel.RemoveFile(fileName, fileInfo.Extension, contextFactory, fileService);
        }
    }

    private async Task OnFileRemove(RemovingEventArgs args)
    {
        foreach (var file in args.FilesData)
        {
            var fileName = file.Name;
            System.IO.FileInfo fileInfo = new System.IO.FileInfo(fileName);
            itemModel.RemoveFile(fileName, fileInfo.Extension, contextFactory, fileService);
        }
    }

    private async Task OnSelectedItemChanged(ChangeEventArgs<int, spGetNewsCategoriesResult> eventArgs)
    {
        itemModel.Category = eventArgs.Value;
        StateHasChanged();
    }

    private async Task OnSave()
    {
        if (isChecked)
        {
            itemModel.RequestDates(activeFrom, activeTo);
        }

        var context = contextFactory.CreateDbContext();
        var procedures = context.Procedures;
        var addedFiles = await procedures.spCreateNewsItemAsync(JsonConvert.SerializeObject(itemModel), userName);

        if (addedFiles.Count > 0)
        {
            foreach (var file in addedFiles)
            {
                await fileService.MoveTempToNewsAsync(file.fileName, file.newsID, file.fileID);
            }
        }

        Message = "This has been successfully saved and is now pending review; pressing OK will refresh the page.";
        OKText = "OK";
        ShowDialog = true;
    }

    private async Task OnDialogClose(bool r)
    {
        ShowDialog = false;
        NavManager.NavigateTo(NavManager.Uri, true);
    }
}

我的问题是此时出现错误:OnSelectedItemChanged="@(OnSelectedItemChanged)"

错误是:

Cannot convert from method group to EventCallback

我所做的搜索似乎暗示我需要明确地将类型作为参数传递,而不是使用 object 并尝试在运行时推断它 - 我只是有点糊涂关于具体怎么做?

TValue 成为 int 是任何地方都不应该改变的。但是 TItem 几乎可以是任何东西(在这个特定的场景中它是 spGetNewsCategoriesResult)——我该如何满足它?

经过大量搜索和修改,我找到了解决方案。通过将组件更改为:

@typeparam T

<div class="inputItem @(SizeClass) dropdown" style="min-width:@(Width);">
    <SfDropDownList TItem="T" TValue="int" Placeholder="Select a category" DataSource="DataSource" Value="@(SelectedItem)" EnableVirtualization="true">
        <DropDownListEvents TItem="T" TValue="int" ValueChange="@OnSelectedItemChanged"></DropDownListEvents>
        <DropDownListFieldSettings Text="@(TextField)" Value="@(ValueField)" />
    </SfDropDownList>
</div>

@code {
    [Parameter]
    public IEnumerable<T> DataSource { get; set; }

    [Parameter]
    public EventCallback<Syncfusion.Blazor.DropDowns.ChangeEventArgs<int, T>> OnSelectedItemChanged { get; set; }

    [Parameter]
    public string Placeholder { get; set; }

    [Parameter]
    public string TextField { get; set; }

    [Parameter]
    public int SelectedItem { get; set; }

    [Parameter]
    public string ValueField { get; set; }

    [Parameter]
    public string Width { get; set; }

    [Parameter]
    public string SizeClass { get; set; }

}

并这样引用它:

<DropDownList DataSource="categories" Placeholder="Select a category" SizeClass="half-width" Width="450px" TextField="name" ValueField="id" SelectedItem="@(itemModel.Category)" OnSelectedItemChanged="@(OnSelectedItemChanged)" T="spGetNewsCategoriesResult"></DropDownList>

错误已解决。决定回答我自己的问题,而不是仅仅删除它,因为我认为它可能会在人们自己搜索时弹出。