Blazor DataGrid同一列展示Entity子类不同属性的实现方案
Blazor DataGrid中根据实体子类类型动态显示名称列
问题描述
我持有一个List<Entity>集合,其中包含Monster和Player两个继承自Entity的子类实例。需要在Blazor的DataGrid组件中展示该集合,并且“名称”列要根据实体类型显示对应值:
- 当实体为
Monster时,显示Monster.EntityName - 当实体为
Player时,显示Player.Name(父类Entity的Name属性)
想确认:
- 是否可以直接实现该需求,无需额外创建EntityDataModel?
- 如果必须创建EntityDataModel,能否保留
List<Entity>作为核心数据源?
相关模型代码
Entity模型
public class Entity { public string Name { get; set; } = string.Empty; public int Id { get; set; } = -1; public int MaxHp { get; set; } = 0; public int Hp { get; set; } = 0; public List<int> Damage { get; set; } = new List<int> { }; public int Init = 0; public int Ac { get; set; } = 10; public string ArmorType { get; set; } = "None"; public string[] Speed { get; set; } = new string[0]; public string[] DmgVul { get; set; } = new string[0]; public string[] DmgRes { get; set; } = new string[0]; public string[] DmgImm { get; set; } = new string[0]; public string[] CondImm { get; set; } = new string[0]; public string[] Senses { get; set; } = new string[0]; public string[] Languages { get; set; } = new string[0]; public List<NameValueCondition> Conditions { get; set; } = new List<NameValueCondition> { }; public EntityHealthState HealthState => GetHealthState(); private EntityHealthState GetHealthState() { if (Hp > MaxHp) return EntityHealthState.Overhealed; else if (Hp <= 0) return EntityHealthState.Dead; else if (Hp <= MaxHp / 4.0) return EntityHealthState.Critical; else if (Hp <= MaxHp / 2.0) return EntityHealthState.Bloody; else if (Hp < MaxHp) return EntityHealthState.Wounded; else return EntityHealthState.Healthy; } } public enum EntityHealthState { Overhealed, Healthy, Wounded, Bloody, Critical, Dead }
Player模型
public class Player : Entity { public bool InParty { get; set; } public int InitiativeBonus { get; set; } }
Monster模型
public class Monster : Entity { public string EntityName { get; set; } = string.Empty; public string Type { get; set; } = string.Empty; public string Alignment { get; set; } = string.Empty; public int HitDiceNumber { get; set; } = 0; public int HitDiceSize { get; set; } = 0; public int HitDiceHp { get; set; } = 0; public int Str { get; set; } = 10; public int Dex { get; set; } = 10; public int Con { get; set; } = 10; public int Int { get; set; } = 10; public int Wis { get; set; } = 10; public int Cha { get; set; } = 10; public string[] SavThrProf { get; set; } = new string[0]; public string[] SkillProf { get; set; } = new string[0]; public ChallengeRating Challenge { get; set; } = new ChallengeRating(); public NameValuePair[] Traits { get; set; } = new NameValuePair[0]; public NameValuePair[] Actions { get; set; } = new NameValuePair[0]; public bool isSpellCaster { get; set; } = false; public int SpellSlots1 { get; set; } = 0; public List<NameValuePair> Spells1 { get; set; } = new List<NameValuePair> { }; public int SpellSlots2 { get; set; } = 0; public List<NameValuePair> Spells2 { get; set; } = new List<NameValuePair> { }; public int SpellSlots3 { get; set; } = 0; public List<NameValuePair> Spells3 { get; set; } = new List<NameValuePair> { }; public int SpellSlots4 { get; set; } = 0; public List<NameValuePair> Spells4 { get; set; } = new List<NameValuePair> { }; public int SpellSlots5 { get; set; } = 0; public List<NameValuePair> Spells5 { get; set; } = new List<NameValuePair> { }; public int SpellSlots6 { get; set; } = 0; public List<NameValuePair> Spells6 { get; set; } = new List<NameValuePair> { }; public int SpellSlots7 { get; set; } = 0; public List<NameValuePair> Spells7 { get; set; } = new List<NameValuePair> { }; public int SpellSlots8 { get; set; } = 0; public List<NameValuePair> Spells8 { get; set; } = new List<NameValuePair> { }; public int SpellSlots9 { get; set; } = 0; public List<NameValuePair> Spells9 { get; set; } = new List<NameValuePair> { }; public bool isLegendary { get; set; } = false; public NameValuePair[] LegendaryActions { get; set; } = new NameValuePair[0]; public string LegendaryDesc { get; set; } = string.Empty; public string LairActions { get; set; } = string.Empty; public string RegionalEffects { get; set; } = string.Empty; public bool IsKnown = true; public bool IsUnseen = false; public bool IsAlly = false; }
已尝试的实现方式
<GridView Data="@entities"> <Columns> <GridViewColumn> <Header>Name</Header> <RenderFragment Context="context"> @if (context is Monster monster) { @monster.EntityName } else if (context is Player player) { @player.Name } </RenderFragment> </GridViewColumn> </Columns> </GridView> <table> <thead> <tr> <th>Name</th> <th>Remove</th> </tr> </thead> <tbody> @foreach (var entity in entities) { <tr> @if (entity is Monster) { var monster = entity as Monster; <td>@monster.EntityName</td> } else { <td>@entity.Name</td> } <td> <button @onclick="() => RemoveMonster(entity)"> Remove </button> </td> </tr> } </tbody> </table>
普通表格可以实现需求,但希望用DataGrid组件完成。
解决方案
1. 直接实现(无需EntityDataModel)
绝大多数Blazor DataGrid组件(如Radzen DataGrid、MudBlazor MudDataGrid、DevExpress Blazor Grid)都支持模板列功能,你可以在列的模板中直接进行类型判断,动态渲染对应的值,无需额外创建数据模型。
以你使用的GridView组件为例,修正模板写法即可:
<GridView Data="@entities"> <Columns> <GridViewColumn Title="名称"> <CellTemplate Context="entity"> @{ if (entity is Monster monster) { @monster.EntityName } else if (entity is Player player) { @player.Name } else { @entity.Name } } </CellTemplate> </GridViewColumn> <!-- 其他列可以直接绑定Entity的公共属性,例如: --> <GridViewColumn Title="生命值" Field="@nameof(Entity.Hp)" /> <GridViewColumn Title="最大生命值" Field="@nameof(Entity.MaxHp)" /> </Columns> </GridView>
如果是其他组件(比如MudBlazor),写法类似:
<MudDataGrid Items="@entities"> <Columns> <MudDataGridColumn HeaderText="名称"> <CellTemplate> @{ var entity = context.Item as Entity; if (entity is Monster m) { @m.EntityName } else if (entity is Player p) { @p.Name } else { @entity.Name } } </CellTemplate> </MudDataGridColumn> </Columns> </MudDataGrid>
2. 若需创建EntityDataModel,仍可保留原List作为核心数据源
如果因为排序、过滤或统一数据结构的需求必须创建EntityDataModel,你可以通过包装原Entity引用的方式,保留原List<Entity>作为核心数据源:
步骤1:创建EntityDataModel
public class EntityDataModel { // 保留原Entity的引用,确保核心数据源不变 public Entity OriginalEntity { get; set; } = null!; // 动态计算显示名称 public string DisplayedName => OriginalEntity switch { Monster monster => monster.EntityName, Player player => player.Name, _ => OriginalEntity.Name }; // 其他需要展示的属性,直接从原Entity映射 public int Hp => OriginalEntity.Hp; public int MaxHp => OriginalEntity.MaxHp; public EntityHealthState HealthState => OriginalEntity.HealthState; // ... 按需添加其他属性 }
步骤2:在组件中动态生成DataGrid数据源
@code { // 核心数据源,保持原List<Entity>不变 private List<Entity> entities = new(); // 动态投影成DataGrid需要的模型,无需复制数据,仅包装引用 private List<EntityDataModel> DataGridDataSource => entities.Select(e => new EntityDataModel { OriginalEntity = e }).ToList(); } <!-- 使用DataGrid绑定投影后的数据源 --> <GridView Data="@DataGridDataSource"> <Columns> <GridViewColumn Title="名称" Field="@nameof(EntityDataModel.DisplayedName)" /> <GridViewColumn Title="生命值" Field="@nameof(EntityDataModel.Hp)" /> <!-- 其他列绑定DataModel的属性 --> </Columns> </GridView>
这种方式下,所有对实体的修改仍直接操作原List<Entity>,DataGrid仅通过DataModel做显示层的映射,既满足了DataGrid的需求,也保留了核心数据源的完整性。
内容的提问来源于stack exchange,提问作者NoTimeLeft
相关产品推荐
相关产品推荐

