();
+ string? currentColumnName;
+ string? currentFormatId;
+
+ string SaveButtonText =>
+ IsEmptyFilterCriteriaSelected
+ ? "Select a Filter"
+ : (isNew ? "Add Format" : "Save Changes");
+
+ bool isNew = true;
+ bool IsEmptyFilterCriteriaSelected =>
+ selectedConditionType is ConditionType.Filter &&
+ (selectedFilterCriteria is null || string.IsNullOrEmpty(selectedFilterCriteria.ToString()));
+
+ ConditionType selectedConditionType;
+ CriteriaOperator? selectedFilterCriteria;
+ string? selectedFormatStyleName;
+
+ protected override void OnInitialized() {
+ conditions.Add(new ConditionInfo(ConditionType.Filter, "Filter"));
+ if (SummaryConditions?.Any() == true)
+ conditions.AddRange(SummaryConditions);
+ }
+
+ void OnApplyClick() {
+ bool isValidCondition = selectedFilterCriteria is not null || selectedConditionType is not ConditionType.Filter;
+ var condition = conditions.FirstOrDefault(c => c.ConditionType == selectedConditionType);
+
+ if (condition is not null && currentColumnName is not null && isValidCondition) {
+ if (condition.ConditionType is ConditionType.Filter)
+ condition = new FilterConditionInfo(condition.ConditionName, selectedFilterCriteria!);
+
+ if (isNew) {
+ isNew = false;
+ if (Formats is not null) {
+ var format = new SimpleFormat(
+ currentColumnName,
+ condition,
+ FormatStyles.FirstOrDefault(fs => fs.FormatName == selectedFormatStyleName)
+ );
+ Formats.Add(format);
+ }
+ }
+ else {
+ var format = Formats?.FirstOrDefault(f => f.FormatId == currentFormatId);
+ if (format is not null) {
+ format.Condition = condition;
+ format.Style = FormatStyles.FirstOrDefault(fs => fs.FormatName == selectedFormatStyleName);
+ }
+ }
+ }
+
+ popupRef?.CloseAsync();
+ FormatsChanged.InvokeAsync();
+ }
+
+ private void OnCancelClick() {
+ popupRef?.CloseAsync();
+ }
+
+ private void OnDeleteClick() {
+ var format = Formats?.FirstOrDefault(f => f.FormatId == currentFormatId);
+ if (format is null || Formats is null) {
+ Console.WriteLine($"Could not find format with ID:{currentFormatId} in the Formats collection");
+ return;
+ }
+ Formats.Remove(format);
+ FormatsChanged.InvokeAsync();
+ popupRef?.CloseAsync();
+ }
+
+ public void AddNewFormat(string fieldName) {
+ var firstOrDefaultCondition = conditions.FirstOrDefault();
+ if (firstOrDefaultCondition is null) {
+ Console.WriteLine("No conditions available to show Format Selector");
+ return;
+ }
+
+ currentColumnName = fieldName;
+ currentFormatId = null;
+ isNew = true;
+ selectedConditionType = firstOrDefaultCondition.ConditionType;
+ selectedFilterCriteria = null;
+ selectedFormatStyleName = null;
+
+ StateHasChanged();
+ popupRef?.ShowAsync();
+ }
+
+ public void EditFormat(string formatId) {
+ var formatItem = Formats?.FirstOrDefault(f => f.FormatId == formatId);
+ if (formatItem is null) {
+ Console.WriteLine($"Could not find format with ID:{formatId} in the Formats collection");
+ return;
+ }
+
+ currentColumnName = formatItem.ColumnFieldName;
+ currentFormatId = formatId;
+ isNew = false;
+ selectedConditionType = formatItem.Condition.ConditionType;
+ if (formatItem.Condition is FilterConditionInfo filterInfo)
+ selectedFilterCriteria = filterInfo.FilterCriteria;
+ selectedFormatStyleName = formatItem.Style?.FormatName;
+
+ StateHasChanged();
+ popupRef?.ShowAsync();
+ }
+
+ public void ClearAllColumnFormats(string fieldName) {
+ Formats?.RemoveAll(f => f.ColumnFieldName == fieldName);
+ FormatsChanged.InvokeAsync();
+ }
}
\ No newline at end of file
diff --git a/CS/Components/Pages/Index.razor b/CS/Components/Pages/Index.razor
index 4f8882a..f782e05 100644
--- a/CS/Components/Pages/Index.razor
+++ b/CS/Components/Pages/Index.razor
@@ -1,77 +1,86 @@
-@page "/"
-@rendermode InteractiveServer
-@using ConditionalFormatSelector.Data
-
-Conditional Format Selector Example
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-Applied Formats:
-@foreach (var format in formats) {
- Column: @format.ColumnFieldName - Formatting: @format.ToString()
-}
-
-@code {
- ConditionalFormatSelector? formatSelectorRef;
- List data = GridItem.GetData();
- List formats = SimpleFormat.GenerateTestFormats("Progress", 20, "Red Background", 80, "Green Background");
- string[] visibleFields = new string[] { "Progress", "Code" };
- List summaryConditions = new List
- {
- new SummaryConditionInfo(ConditionType.GreaterThanTotalSummary, "Progress is greater than average", "Progress", "AverageProgress"),
- new SummaryConditionInfo(ConditionType.LessThanTotalSummary, "Progress is less than average", "Progress", "AverageProgress"),
- };
- List formatStyles = ConditionalFormatHelper.GetFormatStyles();
-
- private void CustomizeContextMenu(GridCustomizeContextMenuEventArgs args) {
- args.Items.Clear();
-
- if (args.Context is GridHeaderCommandContext headerContext && headerContext.Column is IGridDataColumn column) {
- var formatItem = args.Items.AddCustomItem("Conditional Formatting");
- formatItem.Items.AddCustomItem("Apply New Format", () => { formatSelectorRef?.AddNewFormat(column.FieldName); });
- foreach (SimpleFormat format in formats.Where(f => f.ColumnFieldName == column.FieldName)) {
- formatItem.Items.AddCustomItem(format.ToString(), () => { formatSelectorRef?.EditFormat(format.FormatId); });
- }
- formatItem.Items.AddCustomItem("Clear Formats", () => { formatSelectorRef?.ClearAllColumnFormats(column.FieldName); });
- }
- }
-
- private void OnCustomizeElement(GridCustomizeElementEventArgs args) {
- foreach (var format in formats) {
- format.ApplyStyle(args);
- }
- }
-
- private void OnFormatsChanged() {
- StateHasChanged();
- }
+@page "/"
+@rendermode InteractiveServer
+@using ConditionalFormatSelector.Data
+
+Conditional Format Selector Example
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Applied Rules:
+
+
+ | Column |
+ Format |
+
+ @foreach (var format in formats) {
+
+ | @format.ColumnFieldName |
+ @format.ToString() |
+
+ }
+
+
+@code {
+ ConditionalFormatSelector? formatSelectorRef;
+ List data = GridItem.GetData();
+ List formats = SimpleFormat.GenerateTestFormats("Progress", 20, "Red Background", 80, "Green Background");
+ string[] visibleFields = new string[] { "Progress", "Code" };
+ List summaryConditions = new List
+ {
+ new SummaryConditionInfo(ConditionType.GreaterThanTotalSummary, "Progress is greater than average", "Progress", "AverageProgress"),
+ new SummaryConditionInfo(ConditionType.LessThanTotalSummary, "Progress is less than average", "Progress", "AverageProgress"),
+ };
+ List formatStyles = ConditionalFormatHelper.GetFormatStyles();
+
+ private void CustomizeContextMenu(GridCustomizeContextMenuEventArgs args) {
+ args.Items.Clear();
+
+ if (args.Context is GridHeaderCommandContext headerContext && headerContext.Column is IGridDataColumn column) {
+ var formatItem = args.Items.AddCustomItem("Conditional Formatting");
+ formatItem.Items.AddCustomItem("Apply New Format", () => { formatSelectorRef?.AddNewFormat(column.FieldName); });
+ foreach (SimpleFormat format in formats.Where(f => f.ColumnFieldName == column.FieldName)) {
+ formatItem.Items.AddCustomItem(format.ToString(), () => { formatSelectorRef?.EditFormat(format.FormatId); });
+ }
+ formatItem.Items.AddCustomItem("Clear Formatting", () => { formatSelectorRef?.ClearAllColumnFormats(column.FieldName); });
+ }
+ }
+
+ private void OnCustomizeElement(GridCustomizeElementEventArgs args) {
+ foreach (var format in formats) {
+ format.ApplyStyle(args);
+ }
+ }
+
+ private void OnFormatsChanged() {
+ StateHasChanged();
+ }
}
\ No newline at end of file
diff --git a/CS/Components/Pages/Index.razor.css b/CS/Components/Pages/Index.razor.css
index 50d7e7a..4c9aab2 100644
--- a/CS/Components/Pages/Index.razor.css
+++ b/CS/Components/Pages/Index.razor.css
@@ -1,3 +1,6 @@
::deep .tasks-grid {
height: 80vh;
+}
+table * {
+ border: 1px solid black;
}
\ No newline at end of file
diff --git a/CS/ConditionalFormatSelector.csproj b/CS/ConditionalFormatSelector.csproj
index c17ee51..51fb5a1 100644
--- a/CS/ConditionalFormatSelector.csproj
+++ b/CS/ConditionalFormatSelector.csproj
@@ -5,6 +5,6 @@
enable
-
+
diff --git a/CS/readme.md b/CS/readme.md
deleted file mode 100644
index 8f3300d..0000000
--- a/CS/readme.md
+++ /dev/null
@@ -1,8 +0,0 @@
-App.razor - added conditional format css
-conditional.format.css - describe formatting rules for conditional formatting
-Data - data classes
-ConditionalFormatSelector.razor - selector
-Index.razor - main logic
-
-===
-TODO - generate test formats refactoring.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ba26fca
--- /dev/null
+++ b/README.md
@@ -0,0 +1,176 @@
+# Blazor Grid – Apply Conditional Formatting at Runtime
+
+This example allows users to specify and apply conditional formatting rules at runtime. The format editor appears on-screen when a user right-clicks a column header. Once the user specifies required rules and styles, the [CustomizeElement](https://docs.devexpress.com/Blazor/DevExpress.Blazor.DxGrid.CustomizeElement) event applies desired styles to data cells.
+
+
+
+## Implementation Details
+
+### Define Custom Styles
+
+In the wwwroot/css folder, create a stylesheet to store CSS rules users can apply to Grid regions. We recommend the use of public design tokens when constructing styles. Review the following help topic for additional information in this regard: [CSS Variables in Fluent Themes](https://docs.devexpress.com/Blazor/405705/styling-and-themes/fluent-theme-customization/fluent-css-variables).
+
+To review design token usage in this example, see the following file: [conditional.format.css](./CS/wwwroot/css/conditional.format.css).
+
+### Implement a Format Object
+
+1. Create a [SimpleFormat](./CS/Data/SimpleFormat.cs) class to store the following information:
+ * The target column's [FieldName](https://docs.devexpress.com/Blazor/DevExpress.Blazor.DxGridDataColumn.FieldName)
+ * [Conditions](./CS/Data/ConditionInfo.cs)
+ * [Custom style information](./CS/Data/FormatStyleInfo.cs)
+
+ This example allows users to specify the following condition types:
+ * Build your own condition. This example uses a standalone Filter Builder component for the condition builder UI and then evaluates the resulting rule using the [Filter Criteria API](https://docs.devexpress.com/CoreLibraries/17541/devexpress-data-library).
+ * Cell value is greater than or less than the total summary (average).
+
+ ```cs
+ public enum ConditionType {
+ Filter,
+ GreaterThanTotalSummary,
+ LessThanTotalSummary
+ }
+
+ public class SimpleFormat {
+ public SimpleFormat(string columnFieldName, ConditionInfo condition, FormatStyleInfo? style) {
+ FormatId = Guid.NewGuid().ToString();
+ ColumnFieldName = columnFieldName;
+ Condition = condition;
+ Style = style;
+ }
+ public string FormatId { get; }
+ public ConditionInfo Condition { get; set; }
+ public FormatStyleInfo? Style { get; set; }
+ public string ColumnFieldName { get; set; }
+ }
+ ```
+
+1. [Evaluate](./CS/Data/ConditionalFormatHelper.cs#L16) whether the style should be applied to the [current element](./CS/Data/GridItem.cs) based on the [specified condition](./CS/Data/ConditionInfo.cs).
+
+ ```cs
+ public static bool Evaluate(SimpleFormat format, GridCustomizeElementEventArgs args) {
+ switch(format.Condition.ConditionType) {
+ case ConditionType.Filter:
+ return EvaluateFilterCriteria(format, args);
+ case ConditionType.GreaterThanTotalSummary:
+ return EvaluateGreaterThanTotalSummary(format, args);
+ case ConditionType.LessThanTotalSummary:
+ return EvaluateLessThanTotalSummary(format, args);
+ default:
+ return false;
+ }
+ }
+ ```
+
+ Review the following file for implementation details: [ConditionalFormatHelper.cs](./CS/Data/ConditionalFormatHelper.cs).
+
+1. In the [SimpleFormat](./CS/Data/SimpleFormat.cs) class, implement a method that calls the evaluation method and applies CSS rules.
+
+ ```cs
+ public class SimpleFormat {
+ public void ApplyStyle(GridCustomizeElementEventArgs args) {
+ bool isTargetDataCell = args.ElementType == GridElementType.DataCell && args.Column is IGridDataColumn column && column.FieldName == ColumnFieldName;
+ if(isTargetDataCell) {
+ bool shouldApply = ConditionalFormatHelper.Evaluate(this, args);
+ if(shouldApply) {
+ if(!string.IsNullOrEmpty(args.CssClass))
+ args.CssClass += " ";
+ if(Style != null)
+ args.CssClass += Style.CssClassName;
+ }
+ }
+ }
+ }
+ ```
+
+### Create a Format Selector Component
+
+1. Create a Razor component (the UI designed to configure conditions and styles).
+1. Check if specified conditions are valid.
+1. Process "Apply", "Cancel", and "Remove" operations. After each operation, invoke the [FormatsChanged](./CS/Components/ConditionalFormatSelector.razor#L49) callback.
+1. Add the newly created component to the _Index.razor_ page. In the `FormatsChanged` handler, call the `StateHasChanged` method to re-render the page after new styles are applied.
+
+ ```html
+
+
+ @code {
+ private void OnFormatsChanged() {
+ StateHasChanged();
+ }
+ }
+ ```
+
+Review the following file for implementation details: [ConditionalFormatSelector.razor](./CS/Components/ConditionalFormatSelector.razor).
+
+### Set Up a Grid
+
+1. Add a `DxGrid` to the _Index.razor_ page and populate it with data.
+1. Add a [TotalSummary](https://docs.devexpress.com/Blazor/404471/components/grid/data-shaping/summary) that will be used as a reference value in summary-based conditions.
+1. Enable the Grid's context menu using the [ContextMenus](https://docs.devexpress.com/Blazor/DevExpress.Blazor.DxGrid.ContextMenus) property.
+1. Handle the [CustomizeContextMenu](https://docs.devexpress.com/Blazor/DevExpress.Blazor.DxGrid.CustomizeContextMenu) event to add custom items to the context menu (**Conditional Formatting**, **Apply New Format**, and **Clear Formatting**).
+1. Process context menu item clicks.
+1. Handle the [CustomizeElement](https://docs.devexpress.com/Blazor/DevExpress.Blazor.DxGrid.CustomizeElement) event to apply user-specified styles.
+
+```html
+
+
+ @* ... *@
+
+
+
+
+
+
+@code {
+ List formats = SimpleFormat.GenerateTestFormats("Progress", 20, "Red Background", 80, "Green Background");
+
+ private void CustomizeContextMenu(GridCustomizeContextMenuEventArgs args) {
+ args.Items.Clear();
+
+ if (args.Context is GridHeaderCommandContext headerContext && headerContext.Column is IGridDataColumn column) {
+ var formatItem = args.Items.AddCustomItem("Conditional Formatting");
+ formatItem.Items.AddCustomItem("Apply New Format", () => { formatSelectorRef?.AddNewFormat(column.FieldName); });
+ foreach (SimpleFormat format in formats.Where(f => f.ColumnFieldName == column.FieldName)) {
+ formatItem.Items.AddCustomItem(format.ToString(), () => { formatSelectorRef?.EditFormat(format.FormatId); });
+ }
+ formatItem.Items.AddCustomItem("Clear Formatting", () => { formatSelectorRef?.ClearAllColumnFormats(column.FieldName); });
+ }
+ }
+
+ private void OnCustomizeElement(GridCustomizeElementEventArgs args) {
+ foreach (var format in formats) {
+ format.ApplyStyle(args);
+ }
+ }
+}
+```
+
+## Files to Review
+
+- [Index.razor](./CS/Components/Pages/Index.razor)
+- [ConditionalFormatSelector.razor](./CS/Components/ConditionalFormatSelector.razor)
+- [ConditionalFormatHelper.cs](./CS/Data/ConditionalFormatHelper.cs)
+- [SimpleFormat.cs](./CS/Data/SimpleFormat.cs)
+- [ConditionInfo.cs](./CS/Data/ConditionInfo.cs)
+- [FormatStyleInfo.cs](./CS/Data/FormatStyleInfo.cs)
+- [conditional.format.css](./CS/wwwroot/css/conditional.format.css)
+
+## Documentation
+
+- [DevExpress Design System Foundation](https://docs.devexpress.com/DesignSystem/405636/foundation)
+- [CSS Variables in Fluent Themes](https://docs.devexpress.com/Blazor/405705/styling-and-themes/fluent-theme-customization/fluent-css-variables)
+- [DxGrid.CustomizeElement](https://docs.devexpress.com/Blazor/DevExpress.Blazor.DxGrid.CustomizeElement)
+- [DxGrid.CustomizeContextMenu](https://docs.devexpress.com/Blazor/DevExpress.Blazor.DxGrid.CustomizeContextMenu)
+
+## More Examples
+
+- [Blazor Grid - Customize cell appearance based on custom conditions](https://github.com/DevExpress-Examples/blazor-grid-conditional-formatting)
+- [Blazor Grid - Customize Context Menu](https://github.com/DevExpress-Examples/blazor-grid-show-context-menu)
diff --git a/result.png b/result.png
new file mode 100644
index 0000000..9853b89
Binary files /dev/null and b/result.png differ