ValueStyle¶
Namespace: ThinkGeo.Core
Applies to: All ThinkGeo products (WPF, WinForms, Blazor, MAUI, GIS Server, etc.)
Overview¶
ValueStyle renders features using different styles depending on whether a feature's attribute column value exactly matches a set of predefined strings. Each match entry — called a ValueItem — pairs a string value with a style to apply when the column value equals it.
Where ClassBreakStyle handles continuous numeric ranges, ValueStyle handles discrete categorical data: crime type codes, land-use classifications, geoprocessing result tags, road classes, or any other column whose values are a finite set of known strings. It is the standard choice whenever features need to be colour-coded by category.
Constructors¶
There are two constructor forms in the samples.
Default constructor + ColumnName property¶
Constructs an empty style, then sets the column name separately. Used when the column is known up front and ValueItem entries are added in subsequent statements:
var valueStyle = new ValueStyle();
valueStyle.ColumnName = "DataPoint1";
var valueStyle = new ValueStyle();
valueStyle.ColumnName = "Name";
Sources: wpfHowDoI:Samples/Miscellaneous/PerfTestRefreshShapes.xaml.cs, blazorHowDoI:Shared/GeometricFunctionHelper.cs.
Constructor with column name and ValueItem collection¶
Passes both the column name and a pre-built Collection<ValueItem> in one call. Used when ValueItem objects are assembled in a loop before the ValueStyle is created:
var valueStyle = new ValueStyle("OffenseGro", valueItems);
Sources: wpfHowDoI:Samples/VectorDataStyling/RenderBasedOnValues.xaml.cs, winformHowDoI:Samples/VectorDataStyling/RenderBasedOnValues.cs, mauiHowDoI:Samples/VectorDataStyling/CreateValueStyle.xaml.cs.
Properties¶
ColumnName¶
string — The feature attribute column whose value is matched against each ValueItem.Value at render time. Must match the exact column name in the layer's data source (case-sensitive). Set in the constructor or directly on the style object.
ValueItems¶
Collection<ValueItem> — The list of value-to-style mappings. At render time, ThinkGeo looks up the feature's column value in this collection and applies the matching item's style. If no item matches, the feature is rendered with the first ValueItem whose Value is an empty string (""), if one exists — this acts as the default/fallback style.
The ValueItem Object¶
Each entry in ValueItems is a ValueItem. There are two constructor forms.
Single-style constructor¶
Takes a string match value and a single Style:
new ValueItem("1", new AreaStyle(new GeoPen(GeoColors.Black, 1f), new GeoSolidBrush(new GeoColor(50, GeoColors.Red))))
new ValueItem("Buffering", new AreaStyle(new GeoSolidBrush(GeoColor.FromArgb(140, 255, 155, 13))))
new ValueItem("OffenseName", PointStyle.CreateSimpleCircleStyle(color, 10, GeoColors.Black, 2))
The style can be any concrete style type — AreaStyle, PointStyle, LineStyle, or TextStyle.
Default/fallback ValueItem — empty string Value with CustomStyles¶
When a feature's column value does not match any named ValueItem, ThinkGeo falls back to the ValueItem whose Value is "". This item uses its CustomStyles collection (rather than a single constructor style) so that multiple style types can be stacked — useful when a layer holds mixed geometry types and needs a fallback for each:
ValueItem defaultValueItem = new ValueItem();
defaultValueItem.Value = "";
defaultValueItem.CustomStyles.Add(new LineStyle(new GeoPen(GeoColor.FromArgb(180, 255, 155, 13), 5)));
defaultValueItem.CustomStyles.Add(new PointStyle(PointSymbolType.Circle, 8,
new GeoSolidBrush(GeoColor.FromArgb(255, 255, 248, 172)), GeoPens.Black));
defaultValueItem.CustomStyles.Add(new AreaStyle(new GeoPen(GeoColors.Green, 3),
new GeoSolidBrush(GeoColor.FromArgb(100, 0, 147, 221))));
valueStyle.ValueItems.Add(defaultValueItem);
The empty-string item must be added first (before the named items) so that specific matches take priority.
Source: blazorHowDoI:Shared/GeometricFunctionHelper.cs.
Attaching to a Layer¶
ValueStyle is always added to a zoom level's CustomStyles collection with ApplyUntilZoomLevel:
layer.ZoomLevelSet.ZoomLevel01.CustomStyles.Add(valueStyle);
layer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;
Multiple ValueStyle instances can be added to the same CustomStyles collection — they all evaluate and render independently.
Pattern: Categorical Point Styling Driven by Distinct Column Values¶
The dominant pattern across WPF, WinForms, and MAUI. The column's distinct values are fetched from the live data source, a color is generated for each, and a ValueItem is built per value — all before constructing the ValueStyle:
private static void AddValueStyle(FeatureLayer friscoCrime, LegendAdornmentLayer legend)
{
// 1. Fetch all distinct values in the column
friscoCrime.Open();
var offenseGroups = friscoCrime.FeatureSource.GetDistinctColumnValues("OffenseGro");
friscoCrime.Close();
// 2. Generate one color per distinct value
var colors = GeoColor.GetColorsInQualityFamily(GeoColors.Red, offenseGroups.Count);
// 3. Build ValueItems and matching LegendItems in a single loop
var valueItems = new Collection<ValueItem>();
foreach (var offenseGroup in offenseGroups)
{
// One PointStyle per category, colored from the generated palette
var style = PointStyle.CreateSimpleCircleStyle(
colors[offenseGroups.IndexOf(offenseGroup)], 10, GeoColors.Black, 2);
valueItems.Add(new ValueItem(offenseGroup.ColumnValue, style));
// Mirror in the legend
var legendItem = new LegendItem()
{
ImageStyle = style,
TextStyle = new TextStyle(offenseGroup.ColumnValue, new GeoFont("Verdana", 10), GeoBrushes.Black)
};
legend.LegendItems.Add(legendItem);
}
// 4. Construct the ValueStyle from the column name and collected items
var valueStyle = new ValueStyle("OffenseGro", valueItems);
// 5. Apply to the layer
friscoCrime.ZoomLevelSet.ZoomLevel01.CustomStyles.Add(valueStyle);
friscoCrime.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;
}
Sources: wpfHowDoI:Samples/VectorDataStyling/RenderBasedOnValues.xaml.cs, winformHowDoI:Samples/VectorDataStyling/RenderBasedOnValues.cs, mauiHowDoI:Samples/VectorDataStyling/CreateValueStyle.xaml.cs.
Pattern: Static Polygon Styling with Named Integer Values¶
When feature column values are small integers stored as strings, each integer maps to an AreaStyle. The ValueStyle is constructed with the default constructor and ValueItems are appended directly:
var valueStyle = new ValueStyle
{
ColumnName = "DataPoint1"
};
valueStyle.ValueItems.Add(new ValueItem("1",
new AreaStyle(new GeoPen(GeoColors.Black, 1f), new GeoSolidBrush(new GeoColor(50, GeoColors.Red)))));
valueStyle.ValueItems.Add(new ValueItem("2",
new AreaStyle(new GeoPen(GeoColors.Black, 1f), new GeoSolidBrush(new GeoColor(50, GeoColors.Blue)))));
valueStyle.ValueItems.Add(new ValueItem("3",
new AreaStyle(new GeoPen(GeoColors.Black, 1f), new GeoSolidBrush(new GeoColor(50, GeoColors.Green)))));
valueStyle.ValueItems.Add(new ValueItem("4",
new AreaStyle(new GeoPen(GeoColors.Black, 1f), new GeoSolidBrush(new GeoColor(50, GeoColors.White)))));
polygonLayer.ZoomLevelSet.ZoomLevel01.CustomStyles.Add(valueStyle);
polygonLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;
Sources: wpfHowDoI:Samples/Miscellaneous/PerfTestRefreshShapes.xaml.cs, winformHowDoI:Samples/Miscellaneous/PerfTestRefreshShapes.cs.
Pattern: Mixed-Geometry Result Layer with Default Fallback and Named Overrides¶
Used in the Blazor geoprocessing sample where a single InMemoryFeatureLayer holds features of multiple geometry types (points, lines, polygons) tagged by a "Name" column. A fallback ValueItem with Value = "" handles all geometry types generically; named items override specific geoprocessing operation results with tailored styles. A TextStyle is stacked in the same CustomStyles collection to label the results:
ValueStyle valueStyle = new ValueStyle();
valueStyle.ColumnName = "Name";
// Fallback item: stacks line, point, and area styles to cover all geometry types
ValueItem defaultValueItem = new ValueItem();
defaultValueItem.Value = "";
defaultValueItem.CustomStyles.Add(new LineStyle(new GeoPen(GeoColor.FromArgb(180, 255, 155, 13), 5)));
defaultValueItem.CustomStyles.Add(new PointStyle(PointSymbolType.Circle, 8,
new GeoSolidBrush(GeoColor.FromArgb(255, 255, 248, 172)), GeoPens.Black));
defaultValueItem.CustomStyles.Add(new AreaStyle(new GeoPen(GeoColors.Green, 3),
new GeoSolidBrush(GeoColor.FromArgb(100, 0, 147, 221))));
// Named overrides for specific result types
valueStyle.ValueItems.Add(defaultValueItem);
valueStyle.ValueItems.Add(new ValueItem("GetLineOnLineResult",
new LineStyle(new GeoPen(GeoColor.FromArgb(200, 146, 203, 252), 5f), new GeoPen(GeoColors.Black, 6f))));
valueStyle.ValueItems.Add(new ValueItem("Buffering",
new AreaStyle(new GeoSolidBrush(GeoColor.FromArgb(140, 255, 155, 13)))));
valueStyle.ValueItems.Add(new ValueItem("ClippingResult",
new AreaStyle(new GeoPen(GeoColors.Black, 1), new GeoSolidBrush(new GeoColor(160, 255, 248, 172)))));
valueStyle.ValueItems.Add(new ValueItem("SnappingBuffer",
new AreaStyle(new GeoSolidBrush(GeoColors.Transparent))));
valueStyle.ValueItems.Add(new ValueItem("EnvelopeResult",
new AreaStyle(new GeoPen(GeoColor.FromArgb(255, 255, 155, 13), 3),
new GeoSolidBrush(new GeoColor(160, 255, 248, 172)))));
valueStyle.ValueItems.Add(new ValueItem("SimplifiedPolygon",
new AreaStyle(new GeoPen(GeoColor.FromArgb(255, 255, 155, 13), 2),
new GeoSolidBrush(GeoColor.FromArgb(140, 255, 155, 13)))));
// ... additional named items ...
// A TextStyle stacked alongside the ValueStyle for result labels
outputLayer.ZoomLevelSet.ZoomLevel01.CustomStyles.Add(valueStyle);
outputLayer.ZoomLevelSet.ZoomLevel01.CustomStyles.Add(
new TextStyle("Display", new GeoFont("Arial", 10), new GeoSolidBrush(GeoColors.Black)));
outputLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;
Source: blazorHowDoI:Shared/GeometricFunctionHelper.cs.
Pattern: Multiple ValueStyle Instances on One Layer¶
When different subsets of values on the same column need completely independent style logic, two ValueStyle objects can be stacked in CustomStyles. Each evaluates independently:
// Primary ValueStyle — handles most named values
ValueStyle valueStyle = new ValueStyle();
valueStyle.ColumnName = "Name";
valueStyle.ValueItems.Add(defaultValueItem);
valueStyle.ValueItems.Add(new ValueItem("Community", ...));
valueStyle.ValueItems.Add(new ValueItem("SnappingBuffer", ...));
valueStyle.ValueItems.Add(new ValueItem("FirePoint", firePointStyle));
// Secondary ValueStyle — handles one additional value with different style logic
ValueStyle valueStyle1 = new ValueStyle();
valueStyle1.ColumnName = "Name";
valueStyle1.ValueItems.Add(new ValueItem("Clipping",
new AreaStyle(new GeoPen(GeoColor.FromArgb(255, 128, 128, 255), 2),
new GeoSolidBrush(GeoColor.FromArgb(100, 146, 203, 252)))));
inputLayer.ZoomLevelSet.ZoomLevel01.CustomStyles.Add(valueStyle);
inputLayer.ZoomLevelSet.ZoomLevel01.CustomStyles.Add(valueStyle1);
inputLayer.ZoomLevelSet.ZoomLevel01.CustomStyles.Add(
new TextStyle("Display", new GeoFont("Arial", 10), new GeoSolidBrush(GeoColors.Black)));
inputLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;
Source: blazorHowDoI:Shared/GeometricFunctionHelper.cs.
Pattern: Image Point Style as a ValueItem¶
A PointStyle configured to display a raster image can be used as the style in a ValueItem, making it straightforward to display a custom icon for a specific category:
PointStyle firePointStyle = new PointStyle();
firePointStyle.PointType = PointType.Image;
firePointStyle.Image = new GeoImage(Path.Combine(imageDirectory, "fire.png"));
valueStyle.ValueItems.Add(new ValueItem("FirePoint", firePointStyle));
Source: blazorHowDoI:Shared/GeometricFunctionHelper.cs.
Wiring to a LegendAdornmentLayer¶
The same loop that creates ValueItem objects also creates the corresponding LegendItem entries. LegendItem.ImageStyle takes the same style object used in the ValueItem, ensuring the legend swatch exactly matches what renders on the map:
var legendItem = new LegendItem()
{
ImageStyle = style, // same PointStyle instance used in the ValueItem
TextStyle = new TextStyle(offenseGroup.ColumnValue, new GeoFont("Verdana", 10), GeoBrushes.Black)
};
legend.LegendItems.Add(legendItem);
Sources: wpfHowDoI:Samples/VectorDataStyling/RenderBasedOnValues.xaml.cs, winformHowDoI:Samples/VectorDataStyling/RenderBasedOnValues.cs, mauiHowDoI:Samples/VectorDataStyling/CreateValueStyle.xaml.cs.
FeatureSource.GetDistinctColumnValues¶
When the set of possible values is not known at design time, call GetDistinctColumnValues on the open layer's FeatureSource to retrieve the live distinct values, then build one ValueItem per result. The layer must be opened before calling this and closed afterwards:
friscoCrime.Open();
var offenseGroups = friscoCrime.FeatureSource.GetDistinctColumnValues("OffenseGro");
friscoCrime.Close();
var colors = GeoColor.GetColorsInQualityFamily(GeoColors.Red, offenseGroups.Count);
var valueItems = new Collection<ValueItem>();
foreach (var offenseGroup in offenseGroups)
{
var style = PointStyle.CreateSimpleCircleStyle(
colors[offenseGroups.IndexOf(offenseGroup)], 10, GeoColors.Black, 2);
valueItems.Add(new ValueItem(offenseGroup.ColumnValue, style));
}
var valueStyle = new ValueStyle("OffenseGro", valueItems);
Sources: wpfHowDoI:Samples/VectorDataStyling/RenderBasedOnValues.xaml.cs, mauiHowDoI:Samples/VectorDataStyling/CreateValueStyle.xaml.cs.
Common Patterns and Pitfalls¶
ValueStyle matches column values as strings, exactly. If the column value is " ROBBERY " (with surrounding spaces) and the ValueItem value is "ROBBERY", they will not match. Trim or normalize values when loading data if matching is unreliable.
The empty-string ValueItem is the fallback, not a catch-all style. It only triggers when no other ValueItem matches. If you want every feature to receive some style, include a Value = "" item. If you omit it, unmatched features render with no style (invisible).
Add the empty-string fallback item first. Internally, the collection is searched in order. Placing the fallback first and named items after is the convention followed in all samples, and ensures that specific overrides are evaluated before the fallback.
ValueItem.CustomStyles supports stacking multiple styles. Unlike the single-style constructor, the default ValueItem constructor exposes a CustomStyles collection. This is the right approach when a fallback item needs to cover all geometry types (line + point + area) on a mixed-geometry layer.
ValueStyle is string-matched, not type-safe. Column values that are numeric in the data (e.g., 1, 2, 3) are still stored and matched as strings. The ValueItem value must be "1", not 1.
Multiple ValueStyle instances on one layer render additively. Each ValueStyle in CustomStyles is evaluated separately and all matching items draw. This can be intentional (as in the two-ValueStyle Blazor pattern) but can also cause unexpected visual layering if a feature matches items in two different styles simultaneously.
Source References¶
| Sample | Namespace | ValueStyle usage |
|---|---|---|
VectorDataStyling/RenderBasedOnValues | WPF, WinForms | GetDistinctColumnValues → Collection<ValueItem> loop → constructor form new ValueStyle(column, items); legend wiring |
VectorDataStyling/CreateValueStyle | MAUI | Same pattern; MapView.MapScale + MapView.CenterPoint for initial extent |
Miscellaneous/PerfTestRefreshShapes | WPF, WinForms | Default constructor + ColumnName property; static integer-string ValueItem entries with AreaStyle |
Shared/GeometricFunctionHelper.cs | Blazor | Default constructor + ColumnName; empty-string fallback ValueItem with CustomStyles for mixed geometry; named overrides including TextStyle item and image PointStyle; two ValueStyle instances stacked in same CustomStyles; TextStyle stacked alongside ValueStyle |