Blazor 创建通用下拉菜单

Posted

技术标签:

【中文标题】Blazor 创建通用下拉菜单【英文标题】:Blazor creating a generic drop-down 【发布时间】:2022-01-12 17:36:37 【问题描述】:

我正在尝试创建一个通用的下拉组件,以便在我们的系统中使用。但是,在更改所选项目时绑定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)"

错误是:

无法从method group 转换为EventCallback

我所做的搜寻似乎暗示我需要将类型作为参数显式传递,而不是使用 object 并尝试在运行时推断它 - 我只是有点模糊该怎么做?

TValue 成为int 是不应该在任何地方改变的东西。但是TItem 几乎可以是任何东西(在这种特殊情况下,它是spGetNewsCategoriesResult)——我该如何满足呢?

【问题讨论】:

【参考方案1】:

经过多次寻找和修补,我找到了解决方案。通过将组件更改为:

@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>

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

【讨论】:

以上是关于Blazor 创建通用下拉菜单的主要内容,如果未能解决你的问题,请参考以下文章

通用无限极下拉菜单

如何创建基于另一个下拉菜单的答案出现的下拉菜单

Odoo[12.0] : 如何创建下拉菜单并在下拉菜单中显示所有菜单以及选择多个菜单

yii框架,如何用cmenu创建下拉菜单

Java如何通过下拉菜单进入另一个页面,求大神指导

Excel 如何实现五级下拉菜单联动