LineStyle Guide¶
Namespace: ThinkGeo.Core
Applies to: All ThinkGeo products (WPF, WinForms, Blazor, MAUI, GIS Server, etc.)
Overview¶
LineStyle controls how line and polyline features — roads, railroads, rivers, routes, topology results — are drawn on the map. Its defining characteristic is a three-pen layer model: every LineStyle renders up to three concentric strokes painted back-to-front:
OuterPen— widest stroke, drawn first (forms the visible border/casing)InnerPen— medium stroke, drawn on top of the outer pen (typically the road fill color)CenterPen— narrowest stroke, drawn last (an optional center detail line)
Most everyday uses only fill one or two of these pens. The typical road-casing pattern uses OuterPen (dark border) + InnerPen (light fill). A simple single-stroke line only sets OuterPen via the single-pen constructor.
All GeoPen properties (Color, Width, DashStyle, DashPattern, StartCap, EndCap) work identically on all three pens.
Constructors¶
Single-pen — new LineStyle(outerPen)¶
One GeoPen argument. Produces a plain, solid-color line. The argument maps to OuterPen.
// Plain thin gray line
streetsLayer.ZoomLevelSet.ZoomLevel10.DefaultLineStyle =
new LineStyle(new GeoPen(GeoBrushes.LightGray, 1));
Sources: wpfHowDoI:Samples/VectorDataStyling/RenderBasedOnScales.xaml.cs, winformHowDoI:Samples/VectorDataStyling/RenderBasedOnScales.cs, mauiHowDoI:Samples/VectorDataStyling/StylingAcrossMultipleScales.xaml.cs.
Two-pen — new LineStyle(outerPen, innerPen)¶
The standard road-casing constructor. The outer pen is wider and darker; the inner pen is narrower and lighter, creating a bordered line effect.
// Railroad: dark gray outer casing + white smoke fill
var lineStyle = new LineStyle(new GeoPen(GeoBrushes.DimGray, 10), new GeoPen(GeoBrushes.WhiteSmoke, 6));
// Street at zoom 12: light gray casing + white fill
new LineStyle(new GeoPen(GeoBrushes.LightGray, 2), new GeoPen(GeoBrushes.White, 1))
// Street at zoom 13
new LineStyle(new GeoPen(GeoBrushes.LightGray, 4), new GeoPen(GeoBrushes.White, 2))
// Street at zoom 14: slightly darker casing
new LineStyle(new GeoPen(GeoBrushes.Gray, 5), new GeoPen(GeoBrushes.White, 4))
Sources: wpfHowDoI:Samples/VectorDataStyling/RenderLines.xaml.cs, winformHowDoI:Samples/VectorDataStyling/RenderBasedOnScales.cs, mauiHowDoI:Samples/VectorDataStyling/CreateLineStyle.xaml.cs.
Two-pen with inline cap settings¶
DrawingLineCap can be set as an object initializer inline on the GeoPen:
// Street at zoom 15: rounded caps on both pens
new LineStyle(
new GeoPen(GeoBrushes.LightGray, 4) { EndCap = DrawingLineCap.Round },
new GeoPen(GeoBrushes.White, 2) { EndCap = DrawingLineCap.Round })
// Street at zoom 16
new LineStyle(
new GeoPen(GeoBrushes.Gray, 5) { EndCap = DrawingLineCap.Round },
new GeoPen(GeoBrushes.White, 4) { EndCap = DrawingLineCap.Round })
Source: mauiHowDoI:Samples/VectorDataStyling/StylingAcrossMultipleScales.xaml.cs.
Two-pen with named parameters — dashed/custom pattern¶
Use named parameters for clarity, especially when configuring DashStyle or DashPattern on one pen separately from the other:
var lineStyle = new LineStyle(
outerPen: new GeoPen(GeoColors.Black, 12),
innerPen: new GeoPen(GeoColors.White, 6)
{
DashStyle = LineDashStyle.Custom,
DashPattern = { 18f, 18f },
StartCap = DrawingLineCap.Flat,
EndCap = DrawingLineCap.Flat
}
);
Source: wpfHowDoI:Samples/VectorDataStyling/RenderLines.xaml.cs.
Three-pen — new LineStyle(outerPen, innerPen, centerPen)¶
Used when all three stroke layers are needed — for example, a highway style with a casing (outer), a fill (inner), and a translucent center detail (center). Pen argument order is outer → inner → center.
// Build each pen independently, then combine
GeoPen centerPen = new GeoPen(centerlineColor, centerlineWidth);
centerPen.DashStyle = centerlineDashStyle;
GeoPen innerPen = new GeoPen(innerLineColor, innerLineWidth);
innerPen.DashStyle = innerLineDashStyle;
GeoPen outerPen = new GeoPen(outerLineColor, outerLineWidth);
outerPen.DashStyle = outerLineDashStyle;
// Apply round caps to all three pens uniformly when desired
if (roundCap)
{
centerPen.StartCap = DrawingLineCap.Round;
centerPen.EndCap = DrawingLineCap.Round;
innerPen.StartCap = DrawingLineCap.Round;
innerPen.EndCap = DrawingLineCap.Round;
outerPen.StartCap = DrawingLineCap.Round;
outerPen.EndCap = DrawingLineCap.Round;
}
return new LineStyle(outerPen, innerPen, centerPen);
Source: blazorHowDoI:Shared/LayerBuilder.cs.
Single-pen with semi-transparent color — GeoColor.FromArgb¶
// Semi-transparent orange stroke — used in ValueStyle output layers
new LineStyle(new GeoPen(GeoColor.FromArgb(180, 255, 155, 13), 5))
// Two-pen with ARGB colors
new LineStyle(
new GeoPen(GeoColor.FromArgb(200, 146, 203, 252), 5f),
new GeoPen(GeoColors.Black, 6f))
Source: blazorHowDoI:Shared/GeometricFunctionHelper.cs.
Static Factory Method¶
LineStyle.CreateSimpleLineStyle¶
Shorthand for a solid single-color line. Commonly used when a LineStyle is needed as a property value (e.g., on DefaultLineStyle of topology result layers) and full constructor verbosity isn't needed.
// Solid line, no outline
LineStyle.CreateSimpleLineStyle(GeoColors.Green, 3, false)
LineStyle.CreateSimpleLineStyle(GeoColors.Blue, 3, false)
The bool parameter controls whether a border/casing pen is also applied. false = center line only; true = bordered line.
Sources: winformHowDoI:Samples/VectorDataTopologicalValidation/ValidateLineTopology.cs, mauiHowDoI:Samples/VectorDataTopologicalValidation/LineValidation.xaml.cs, mauiHowDoI:Samples/VectorDataTopologicalValidation/PolygonValidation.xaml.cs.
GeoPen Properties¶
All three pens share the same GeoPen property surface. These are the properties used across the samples:
| Property | Type | Description |
|---|---|---|
Color | GeoColor | Stroke color. Access via ((GeoSolidBrush)pen.Brush).Color or directly as pen.Color. |
Width | float | Stroke width in pixels. |
DashStyle | LineDashStyle | Built-in dash pattern: Solid, Dash, Dot, DashDot, DashDotDot, Custom. |
DashPattern | Collection<float> | Lengths of dashes and gaps when DashStyle = LineDashStyle.Custom. Values alternate dash/gap (e.g., { 18f, 18f } = 18px dash, 18px gap). |
StartCap | DrawingLineCap | Shape of the line start end: Flat, Round, Square. |
EndCap | DrawingLineCap | Shape of the line end: Flat, Round, Square. |
Attaching a Style to a Layer¶
DefaultLineStyle — single-style case¶
friscoRailroad.ZoomLevelSet.ZoomLevel01.DefaultLineStyle =
new LineStyle(new GeoPen(GeoBrushes.DimGray, 6), new GeoPen(GeoBrushes.WhiteSmoke, 4));
friscoRailroad.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;
Source: mauiHowDoI:Samples/VectorDataStyling/CreateLineStyle.xaml.cs.
CustomStyles — when mixing with other style types¶
Use CustomStyles when combining a LineStyle with a TextStyle on the same zoom level, or when you need more than one line style stacked:
var lineStyle = new LineStyle(new GeoPen(GeoBrushes.DimGray, 6), new GeoPen(GeoBrushes.WhiteSmoke, 4));
var textStyle = new TextStyle("FULL_NAME", new GeoFont("Segoe UI", 12, DrawingFontStyles.Bold), GeoBrushes.MidnightBlue)
{
SplineType = SplineType.StandardSplining,
HaloPen = new GeoPen(GeoBrushes.White, 2),
DrawingLevel = DrawingLevel.LabelLevel
};
streetsLayer.ZoomLevelSet.ZoomLevel01.CustomStyles.Add(lineStyle);
streetsLayer.ZoomLevelSet.ZoomLevel01.CustomStyles.Add(textStyle);
streetsLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;
Sources: wpfHowDoI:Samples/VectorDataStyling/Renderlabels.xaml.cs, winformHowDoI:Samples/VectorDataStyling/RenderLabels.cs, mauiHowDoI:Samples/VectorDataStyling/CreateTextStyle.xaml.cs.
Scale-Dependent Street Styling¶
Roads are the canonical use case for scale-dependent LineStyle. The pattern is to assign a different DefaultLineStyle (and matching DefaultTextStyle) to each zoom range, progressively widening both the outer and inner pens as the map zooms in:
// Zoom levels 10-11: thin single-pen line, labels not yet needed
streetsLayer.ZoomLevelSet.ZoomLevel10.DefaultLineStyle =
new LineStyle(new GeoPen(GeoBrushes.LightGray, 1));
streetsLayer.ZoomLevelSet.ZoomLevel10.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level11;
// Zoom level 12: add an inner (fill) pen
streetsLayer.ZoomLevelSet.ZoomLevel12.DefaultLineStyle =
new LineStyle(new GeoPen(GeoBrushes.LightGray, 2), new GeoPen(GeoBrushes.White, 1));
// Zoom level 13: wider + add street name labels
streetsLayer.ZoomLevelSet.ZoomLevel13.DefaultLineStyle =
new LineStyle(new GeoPen(GeoBrushes.LightGray, 4), new GeoPen(GeoBrushes.White, 2));
streetsLayer.ZoomLevelSet.ZoomLevel13.DefaultTextStyle = new TextStyle("FULL_NAME",
new GeoFont("Segoe UI", 6, DrawingFontStyles.Bold), GeoBrushes.Black)
{
SplineType = SplineType.StandardSplining,
HaloPen = new GeoPen(GeoBrushes.White, 2),
DrawingLevel = DrawingLevel.LabelLevel,
GridSize = 20
};
// Zoom level 14: slightly darker casing for more contrast
streetsLayer.ZoomLevelSet.ZoomLevel14.DefaultLineStyle =
new LineStyle(new GeoPen(GeoBrushes.Gray, 5), new GeoPen(GeoBrushes.White, 4));
// Zoom level 15-16: add rounded caps for smooth ends at large scale
streetsLayer.ZoomLevelSet.ZoomLevel15.DefaultLineStyle = new LineStyle(
new GeoPen(GeoBrushes.LightGray, 4) { EndCap = DrawingLineCap.Round },
new GeoPen(GeoBrushes.White, 2) { EndCap = DrawingLineCap.Round });
streetsLayer.ZoomLevelSet.ZoomLevel16.DefaultLineStyle = new LineStyle(
new GeoPen(GeoBrushes.Gray, 5) { EndCap = DrawingLineCap.Round },
new GeoPen(GeoBrushes.White, 4) { EndCap = DrawingLineCap.Round });
Sources: winformHowDoI:Samples/VectorDataStyling/RenderBasedOnScales.cs, wpfHowDoI:Samples/VectorDataStyling/RenderBasedOnScales.xaml.cs, mauiHowDoI:Samples/VectorDataStyling/StylingAcrossMultipleScales.xaml.cs.
Note: There is no
ApplyUntilZoomLevelcall between consecutiveDefaultLineStyleassignments above — each zoom level stands alone.ApplyUntilZoomLevelis only needed when you want one zoom-level's style to cover a range (e.g.,ZoomLevel10.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level11). When you are styling each zoom level individually, omitting it is correct.
Using LineStyle inside ValueStyle¶
LineStyle appears as the style value for line-geometry ValueItem entries in a ValueStyle. The default item covers all geometry types on the layer; named value items override that default for specific column values.
var valueStyle = new ValueStyle();
valueStyle.ColumnName = "Name";
// Default item covers all features — use a permissive line style for the fallback
var defaultValueItem = new ValueItem();
defaultValueItem.Value = "";
defaultValueItem.CustomStyles.Add(
new LineStyle(new GeoPen(GeoColor.FromArgb(180, 255, 155, 13), 5)));
// Named item for a specific geoprocessing result type
valueStyle.ValueItems.Add(new ValueItem(
"GetLineOnLineResult",
new LineStyle(
new GeoPen(GeoColor.FromArgb(200, 146, 203, 252), 5f),
new GeoPen(GeoColors.Black, 6f))));
Source: blazorHowDoI:Shared/GeometricFunctionHelper.cs.
Using LineStyle on InMemoryFeatureLayer Topology Results¶
LineStyle.CreateSimpleLineStyle is the standard choice when styling InMemoryFeatureLayers used for topology validation output, because the layer may contain a mix of geometry types and you just need a visible, readable line color:
// Layer for features that pass validation — green
var validatedFeaturesLayer = new InMemoryFeatureLayer();
validatedFeaturesLayer.ZoomLevelSet.ZoomLevel01.DefaultLineStyle =
LineStyle.CreateSimpleLineStyle(GeoColors.Green, 3, false);
validatedFeaturesLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;
// Layer for features used as the validation filter — blue
var filterFeaturesLayer = new InMemoryFeatureLayer();
filterFeaturesLayer.ZoomLevelSet.ZoomLevel01.DefaultLineStyle =
LineStyle.CreateSimpleLineStyle(GeoColors.Blue, 3, false);
filterFeaturesLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;
Sources: winformHowDoI:Samples/VectorDataTopologicalValidation/ValidateLineTopology.cs, mauiHowDoI:Samples/VectorDataTopologicalValidation/LineValidation.xaml.cs.
Building a LineStyle via Helper Function (Blazor Pattern)¶
When a LineStyle needs to be constructed from UI-driven parameters (colors, widths, dash styles, cap style), the standard Blazor pattern is a private factory function that builds each GeoPen independently and then calls the three-pen constructor:
private static LineStyle CreateSimpleLineStyle(
GeoColor centerlineColor, float centerlineWidth, LineDashStyle centerlineDashStyle,
GeoColor innerLineColor, float innerLineWidth, LineDashStyle innerLineDashStyle,
GeoColor outerLineColor, float outerLineWidth, LineDashStyle outerLineDashStyle,
bool roundCap)
{
GeoPen centerPen = new GeoPen(centerlineColor, centerlineWidth);
centerPen.DashStyle = centerlineDashStyle;
GeoPen innerPen = new GeoPen(innerLineColor, innerLineWidth);
innerPen.DashStyle = innerLineDashStyle;
GeoPen outerPen = new GeoPen(outerLineColor, outerLineWidth);
outerPen.DashStyle = outerLineDashStyle;
if (roundCap)
{
centerPen.StartCap = DrawingLineCap.Round;
centerPen.EndCap = DrawingLineCap.Round;
innerPen.StartCap = DrawingLineCap.Round;
innerPen.EndCap = DrawingLineCap.Round;
outerPen.StartCap = DrawingLineCap.Round;
outerPen.EndCap = DrawingLineCap.Round;
}
return new LineStyle(outerPen, innerPen, centerPen);
}
This function is then called with all-transparent values on unused pens when fewer than three layers are needed:
highwayLayer.ZoomLevelSet.ZoomLevel01.DefaultLineStyle = CreateSimpleLineStyle(
GeoColors.Transparent, 1, LineDashStyle.Solid, // center (unused)
GeoColor.FromArgb(255, 255, 255, 128), 3.2f, LineDashStyle.Solid, // inner fill
GeoColors.LightGray, 5.2f, LineDashStyle.Solid, // outer casing
true); // round caps
Source: blazorHowDoI:Shared/LayerBuilder.cs.
Common Patterns and Pitfalls¶
Outer pen must always be wider than inner pen. The outer pen is drawn first; the inner pen is painted on top. If the inner pen is wider than the outer pen, the inner pen will fully cover it and the casing effect is lost. A rule of thumb: outer pen width ≈ inner pen width + 2 to 4 pixels.
ApplyUntilZoomLevel is additive, not required per-level. When styling each zoom level individually with a different style, you do not need to call ApplyUntilZoomLevel between them. Only call it when a single zoom level should apply across a range (e.g., ZoomLevel10.ApplyUntilZoomLevel = Level11 means levels 10 and 11 share the same style). Setting ApplyUntilZoomLevel.Level20 on the last explicitly styled level is the standard way to ensure coverage from that point to the deepest zoom.
DashPattern only takes effect when DashStyle = LineDashStyle.Custom. Setting DashPattern without also setting DashStyle = LineDashStyle.Custom has no visual effect.
DrawingLineCap.Round vs DrawingLineCap.Flat. Round caps extend slightly beyond the feature endpoint, making the rendered line slightly longer than its geometry. Flat caps stop exactly at the endpoint. For road networks this is usually unnoticeable, but for topology or measurement layers, Flat is more precise.
Refreshing after style changes. Style changes made at runtime have no visible effect until the overlay is refreshed:
await layerOverlay.RefreshAsync();
Source References¶
| Sample | Namespace | LineStyle usage |
|---|---|---|
VectorDataStyling/RenderLines | WPF | Single-pen, two-pen solid, two-pen custom dashed with DashPattern |
VectorDataStyling/CreateLineStyle | MAUI | Two-pen solid via DefaultLineStyle |
VectorDataStyling/RenderBasedOnScales | WPF, WinForms | Scale-dependent DefaultLineStyle across zoom levels 10–15+ |
VectorDataStyling/StylingAcrossMultipleScales | MAUI | Scale-dependent styling with inline EndCap = DrawingLineCap.Round |
VectorDataStyling/RenderLabels / Renderlabels | WPF, WinForms | Two-pen LineStyle + TextStyle in CustomStyles |
VectorDataStyling/CreateTextStyle | MAUI | Two-pen LineStyle + TextStyle in CustomStyles |
Shared/LayerBuilder.cs | Blazor | Three-pen constructor; factory function pattern with DashStyle and DrawingLineCap |
Shared/GeometricFunctionHelper.cs | Blazor | LineStyle in ValueStyle/ValueItem; GeoColor.FromArgb pens |
VectorDataTopologicalValidation/ValidateLineTopology | WinForms | CreateSimpleLineStyle on InMemoryFeatureLayer |
VectorDataTopologicalValidation/LineValidation | MAUI | CreateSimpleLineStyle on InMemoryFeatureLayer |