Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions Dashboard/Controls/FinOpsContent.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -1057,6 +1057,27 @@
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding GrowthDisplay}" Width="110" SortMemberPath="AutoGrowthSort">
<DataGridTextColumn.Header>
<StackPanel Orientation="Horizontal">
<Button Style="{DynamicResource ColumnFilterButtonStyle}" Tag="GrowthDisplay" Click="DatabaseSizesFilter_Click" Margin="0,0,4,0"/>
<TextBlock Text="Auto Growth" FontWeight="Bold" VerticalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding VlfCountDisplay}" Width="80" SortMemberPath="VlfCountSort">
<DataGridTextColumn.Header>
<StackPanel Orientation="Horizontal">
<Button Style="{DynamicResource ColumnFilterButtonStyle}" Tag="VlfCountDisplay" Click="DatabaseSizesFilter_Click" Margin="0,0,4,0"/>
<TextBlock Text="VLF Count" FontWeight="Bold" VerticalAlignment="Center"/>
</StackPanel>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Unify Database Sizes column filters under one handler.

Line 1063 and Line 1071 use DatabaseSizesFilter_Click, while other DatabaseSizesDataGrid columns still use FinOpsFilter_Click. This splits state across two managers, so filters in the same grid won’t compose reliably and refresh can drop one side’s filter state.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dashboard/Controls/FinOpsContent.xaml` around lines 1063 - 1073, Multiple
column filter buttons in the DatabaseSizesDataGrid are wired to two different
click handlers (DatabaseSizesFilter_Click and FinOpsFilter_Click) causing split
filter state; pick a single handler (preferably DatabaseSizesFilter_Click) and
update all column Button Click attributes in FinOpsContent.xaml to use that
handler, then consolidate any filter logic by moving or merging code from
FinOpsFilter_Click into DatabaseSizesFilter_Click (ensure the chosen handler has
the correct RoutedEventHandler signature and preserves any existing behavior),
and remove the now-unused handler to keep filter state unified.

</DataGridTextColumn.Header>
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Right"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding MonthlyCostShare, StringFormat='{}{0:N2}'}" Width="110">
<DataGridTextColumn.Header>
<StackPanel Orientation="Horizontal">
Expand Down
59 changes: 56 additions & 3 deletions Dashboard/Controls/FinOpsContent.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Controls.Primitives;

Check warning on line 19 in Dashboard/Controls/FinOpsContent.xaml.cs

View workflow job for this annotation

GitHub Actions / build

The using directive for 'System.Windows.Controls.Primitives' appeared previously in this namespace

Check warning on line 19 in Dashboard/Controls/FinOpsContent.xaml.cs

View workflow job for this annotation

GitHub Actions / build

The using directive for 'System.Windows.Controls.Primitives' appeared previously in this namespace
using System.Windows.Media;
using Microsoft.Win32;
using PerformanceMonitorDashboard.Helpers;
Expand All @@ -33,6 +34,10 @@
private DateTime _serverInventoryCacheTime;
private decimal _currentServerMonthlyCost;

private DataGridFilterManager<FinOpsDatabaseSizeStats>? _dbSizesFilterMgr;
private Popup? _dbSizeFilterPopup;
private ColumnFilterPopup? _dbSizeFilterPopupContent;

public FinOpsContent()
{
InitializeComponent();
Expand Down Expand Up @@ -70,6 +75,8 @@
TabHelpers.FreezeColumns(ExpensiveQueriesDataGrid, 1);
TabHelpers.FreezeColumns(IndexAnalysisDetailGrid, 1);
TabHelpers.FreezeColumns(HighImpactDataGrid, 1);

_dbSizesFilterMgr = new DataGridFilterManager<FinOpsDatabaseSizeStats>(DatabaseSizesDataGrid);
}
Comment on lines +79 to 80
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Initialize _dbSizesFilterMgr before first refresh path can use it.

ServerSelector.SelectedIndex = 0 (Line 93) can trigger data loading before OnLoaded runs. Then Line 634 dereferences _dbSizesFilterMgr!, which can be null on first load.

Proposed fix
 public FinOpsContent()
 {
     InitializeComponent();
+    _dbSizesFilterMgr = new DataGridFilterManager<FinOpsDatabaseSizeStats>(DatabaseSizesDataGrid);
     Loaded += OnLoaded;
 }
@@
-            _dbSizesFilterMgr = new DataGridFilterManager<FinOpsDatabaseSizeStats>(DatabaseSizesDataGrid);
+            _dbSizesFilterMgr ??= new DataGridFilterManager<FinOpsDatabaseSizeStats>(DatabaseSizesDataGrid);
@@
-                _dbSizesFilterMgr!.UpdateData(data);
+                _dbSizesFilterMgr ??= new DataGridFilterManager<FinOpsDatabaseSizeStats>(DatabaseSizesDataGrid);
+                _dbSizesFilterMgr.UpdateData(data);

As per coding guidelines, "Dashboard/**/*.cs: ... Watch for: null reference risks ...".

Also applies to: 634-635

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dashboard/Controls/FinOpsContent.xaml.cs` around lines 79 - 80, Initialize
_dbSizesFilterMgr before any code path that can trigger a refresh (e.g., before
ServerSelector.SelectedIndex = 0) to avoid null dereference in the refresh path;
specifically, create the DataGridFilterManager<FinOpsDatabaseSizeStats> instance
(assign to _dbSizesFilterMgr) in the constructor or as a field initializer so
that any call to the refresh/load routine (which dereferences
_dbSizesFilterMgr!) from ServerSelector.SelectedIndex or OnLoaded is safe, or
alternatively guard the refresh code that uses _dbSizesFilterMgr (the calls
around lines that dereference _dbSizesFilterMgr!) with a null-check until
initialization is guaranteed.


/// <summary>
Expand Down Expand Up @@ -624,16 +631,62 @@
}
}

DatabaseSizesDataGrid.ItemsSource = data;
DatabaseSizesNoDataMessage.Visibility = data.Count == 0 ? Visibility.Visible : Visibility.Collapsed;
DbSizeCountIndicator.Text = data.Count > 0 ? $"{data.Count} file(s)" : "";
_dbSizesFilterMgr!.UpdateData(data);
UpdateDbSizeCountUI();
}
catch (Exception ex)
{
Logger.Error($"Error loading database sizes: {ex.Message}", ex);
}
}

private void UpdateDbSizeCountUI()
{
var list = DatabaseSizesDataGrid.ItemsSource as System.Collections.IList;
int count = list?.Count ?? 0;
DatabaseSizesNoDataMessage.Visibility = count == 0 ? Visibility.Visible : Visibility.Collapsed;
DbSizeCountIndicator.Text = count > 0 ? $"{count} file(s)" : "";
}

private void DatabaseSizesFilter_Click(object sender, RoutedEventArgs e)
{
if (sender is not Button button || button.Tag is not string columnName) return;

if (_dbSizeFilterPopup == null)
{
_dbSizeFilterPopupContent = new ColumnFilterPopup();
_dbSizeFilterPopupContent.FilterApplied += FilterPopup_DbSizeFilterApplied;
_dbSizeFilterPopupContent.FilterCleared += FilterPopup_DbSizeFilterCleared;
_dbSizeFilterPopup = new Popup
{
Child = _dbSizeFilterPopupContent,
StaysOpen = false,
Placement = PlacementMode.Bottom,
AllowsTransparency = true
};
}

_dbSizesFilterMgr!.Filters.TryGetValue(columnName, out var existingFilter);
_dbSizeFilterPopupContent!.Initialize(columnName, existingFilter);
_dbSizeFilterPopup.PlacementTarget = button;
_dbSizeFilterPopup.IsOpen = true;
}

private void FilterPopup_DbSizeFilterApplied(object? sender, FilterAppliedEventArgs e)
{
if (_dbSizeFilterPopup != null)
_dbSizeFilterPopup.IsOpen = false;

_dbSizesFilterMgr!.SetFilter(e.FilterState);
UpdateDbSizeCountUI();
}

private void FilterPopup_DbSizeFilterCleared(object? sender, EventArgs e)
{
if (_dbSizeFilterPopup != null)
_dbSizeFilterPopup.IsOpen = false;
}

// ============================================
// Application Connections Tab
// ============================================
Expand Down
2 changes: 1 addition & 1 deletion Dashboard/Models/CollectionHealthItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class CollectionHealthItem
public decimal FailureRatePercent { get; set; }
public long TotalRuns7d { get; set; }
public long FailedRuns7d { get; set; }
public int AvgDurationMs { get; set; }
public long AvgDurationMs { get; set; }
public long TotalRowsCollected7d { get; set; }
}
}
136 changes: 136 additions & 0 deletions Dashboard/Services/DataGridFilterManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* Copyright (c) 2026 Erik Darling, Darling Data LLC
*
* This file is part of the SQL Server Performance Monitor.
*
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using PerformanceMonitorDashboard.Models;

namespace PerformanceMonitorDashboard.Services;

/// <summary>
/// Non-generic interface for looking up filter state from a shared dictionary.
/// </summary>
public interface IDataGridFilterManager
{
Dictionary<string, ColumnFilterState> Filters { get; }
void SetFilter(ColumnFilterState filterState);
void UpdateFilterButtonStyles();
}

/// <summary>
/// Manages column filter state, unfiltered data capture, and filter application
/// for a single DataGrid. Eliminates per-grid boilerplate code.
/// </summary>
public class DataGridFilterManager<T> : IDataGridFilterManager
{
private readonly DataGrid _dataGrid;
private readonly Dictionary<string, ColumnFilterState> _filters = new();
private List<T>? _unfilteredData;

public DataGridFilterManager(DataGrid dataGrid)
{
_dataGrid = dataGrid;
}

public Dictionary<string, ColumnFilterState> Filters => _filters;

/// <summary>
/// Called when new data arrives (refresh cycle). Captures unfiltered data,
/// then re-applies any active filters.
/// </summary>
public void UpdateData(List<T> newData)
{
_unfilteredData = newData;

if (!HasActiveFilters())
{
_dataGrid.ItemsSource = newData;
return;
}

ApplyFilters();
}

/// <summary>
/// Applies or removes a filter and re-filters the data.
/// </summary>
public void SetFilter(ColumnFilterState filterState)
{
if (filterState.IsActive)
_filters[filterState.ColumnName] = filterState;
else
_filters.Remove(filterState.ColumnName);

ApplyFilters();
UpdateFilterButtonStyles();
}

private bool HasActiveFilters()
{
return _filters.Count > 0 && _filters.Values.Any(f => f.IsActive);
}

private void ApplyFilters()
{
if (_unfilteredData == null) return;

if (!HasActiveFilters())
{
_dataGrid.ItemsSource = _unfilteredData;
return;
}

var filteredData = _unfilteredData.Where(item =>
{
foreach (var filter in _filters.Values)
{
if (filter.IsActive && !DataGridFilterService.MatchesFilter(item!, filter))
return false;
}
return true;
}).ToList();

_dataGrid.ItemsSource = filteredData;
}

/// <summary>
/// Updates filter icon colors (gold when active, dim when inactive).
/// </summary>
public void UpdateFilterButtonStyles()
{
foreach (var column in _dataGrid.Columns)
{
if (column.Header is StackPanel headerPanel)
{
var filterButton = headerPanel.Children.OfType<Button>().FirstOrDefault();
if (filterButton != null && filterButton.Tag is string columnName)
{
bool hasActive = _filters.TryGetValue(columnName, out var filter) && filter.IsActive;

var textBlock = new TextBlock
{
Text = hasActive ? "\uE16E" : "\uE71C",
FontFamily = new FontFamily("Segoe MDL2 Assets"),
Foreground = hasActive
? new SolidColorBrush(Color.FromRgb(0xFF, 0xD7, 0x00))
: (Brush)Application.Current.FindResource("ForegroundDimBrush")
};
filterButton.Content = textBlock;

filterButton.ToolTip = hasActive && filter != null
? $"Filter: {filter.DisplayText}\n(Click to modify)"
: "Click to filter";
}
}
}
}
}
33 changes: 31 additions & 2 deletions Dashboard/Services/DatabaseService.FinOps.cs
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,10 @@ public async Task<List<FinOpsDatabaseSizeStats>> GetFinOpsDatabaseSizeStatsAsync
state_desc,
volume_mount_point,
volume_total_mb,
volume_free_mb
volume_free_mb,
is_percent_growth,
growth_pct,
vlf_count
FROM collect.database_size_stats
WHERE collection_time =
(
Expand Down Expand Up @@ -364,7 +367,10 @@ ORDER BY
StateDesc = reader.IsDBNull(15) ? "" : reader.GetString(15),
VolumeMountPoint = reader.IsDBNull(16) ? "" : reader.GetString(16),
VolumeTotalMb = reader.IsDBNull(17) ? 0m : Convert.ToDecimal(reader.GetValue(17)),
VolumeFreeMb = reader.IsDBNull(18) ? 0m : Convert.ToDecimal(reader.GetValue(18))
VolumeFreeMb = reader.IsDBNull(18) ? 0m : Convert.ToDecimal(reader.GetValue(18)),
IsPercentGrowth = reader.IsDBNull(19) ? null : (bool?)(Convert.ToInt32(reader.GetValue(19)) == 1),
GrowthPct = reader.IsDBNull(20) ? null : Convert.ToInt32(reader.GetValue(20)),
VlfCount = reader.IsDBNull(21) ? null : Convert.ToInt32(reader.GetValue(21))
});
}
}
Expand Down Expand Up @@ -2540,9 +2546,32 @@ public class FinOpsDatabaseSizeStats
public string VolumeMountPoint { get; set; } = "";
public decimal VolumeTotalMb { get; set; }
public decimal VolumeFreeMb { get; set; }
public bool? IsPercentGrowth { get; set; }
public int? GrowthPct { get; set; }
public int? VlfCount { get; set; }

// FinOps cost — proportional share of server monthly budget
public decimal MonthlyCostShare { get; set; }

public string GrowthDisplay => IsPercentGrowth switch
{
null => "-",
true => GrowthPct.HasValue ? $"{GrowthPct}%" : "-",
false => AutoGrowthMb == 0 ? "Disabled" : $"{AutoGrowthMb:N0} MB"
};
Comment on lines +2556 to +2561
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "DatabaseService.FinOps.cs" | head -5

Repository: erikdarlingdata/PerformanceMonitor

Length of output: 124


🏁 Script executed:

# Get file size first
wc -l ./Dashboard/Services/DatabaseService.FinOps.cs

Repository: erikdarlingdata/PerformanceMonitor

Length of output: 129


🏁 Script executed:

# Read the GrowthDisplay property and surrounding context
sed -n '2540,2580p' ./Dashboard/Services/DatabaseService.FinOps.cs

Repository: erikdarlingdata/PerformanceMonitor

Length of output: 1757


🏁 Script executed:

# Search for the model class definition and property declarations
rg "class.*Growth|GrowthPct|IsPercentGrowth|AutoGrowthMb" ./Dashboard/Services/DatabaseService.FinOps.cs -B 2 -A 2

Repository: erikdarlingdata/PerformanceMonitor

Length of output: 2840


🏁 Script executed:

# Find the SQL query that fetches growth and is_percent_growth
rg "growth|is_percent_growth" ./Dashboard/Services/DatabaseService.FinOps.cs -B 3 -A 3 | head -100

Repository: erikdarlingdata/PerformanceMonitor

Length of output: 1646


🏁 Script executed:

# Search for the query that selects from collect.database_size_stats or the underlying source
rg "FROM collect.database_size_stats|sys\.database_files|is_percent_growth" ./Dashboard/Services/DatabaseService.FinOps.cs -B 5 -A 20 | head -150

Repository: erikdarlingdata/PerformanceMonitor

Length of output: 5024


🏁 Script executed:

# Look for SQL files in the repo that might contain the query definition
find . -type f \( -name "*.sql" -o -name "*.SQL" \) | head -20

Repository: erikdarlingdata/PerformanceMonitor

Length of output: 863


🏁 Script executed:

# Search for any stored procedures or SQL files in Scripts or sql directories
find . -type d \( -name Scripts -o -name sql -o -name SQL \) 2>/dev/null | head -10

Repository: erikdarlingdata/PerformanceMonitor

Length of output: 91


🏁 Script executed:

# Find the file that collects database size stats
grep -l "database_size_stats\|is_percent_growth\|growth_pct" ./install/*.sql

Repository: erikdarlingdata/PerformanceMonitor

Length of output: 315


🏁 Script executed:

# Check the delta framework file which likely contains collection logic
grep -l "growth" ./install/*.sql

Repository: erikdarlingdata/PerformanceMonitor

Length of output: 517


🏁 Script executed:

# Read the database size stats collection script
cat ./install/52_collect_database_size_stats.sql | head -300

Repository: erikdarlingdata/PerformanceMonitor

Length of output: 11573


🏁 Script executed:

# Check if there's more content and look for the growth logic
wc -l ./install/52_collect_database_size_stats.sql

Repository: erikdarlingdata/PerformanceMonitor

Length of output: 126


🏁 Script executed:

# Read more of the SQL to see the rest of the growth_pct logic
sed -n '200,250p' ./install/52_collect_database_size_stats.sql

Repository: erikdarlingdata/PerformanceMonitor

Length of output: 2166


🏁 Script executed:

# Verify that GrowthPct can be 0 by checking the full property definitions one more time
sed -n '2550,2570p' ./Dashboard/Services/DatabaseService.FinOps.cs

Repository: erikdarlingdata/PerformanceMonitor

Length of output: 869


Render disabled percent-growth files as Disabled.

SQL Server allows growth = 0 with is_percent_growth = 1 for percent-growth files with no growth configured. The current display logic shows this as 0%, creating an inconsistency with the fixed-growth case where AutoGrowthMb = 0 displays as Disabled.

🔧 Suggested fix
 public string GrowthDisplay => IsPercentGrowth switch
 {
     null  => "-",
-    true  => GrowthPct.HasValue ? $"{GrowthPct}%" : "-",
+    true  => !GrowthPct.HasValue ? "-"
+            : GrowthPct.Value == 0 ? "Disabled"
+            : $"{GrowthPct.Value}%",
     false => AutoGrowthMb == 0 ? "Disabled" : $"{AutoGrowthMb:N0} MB"
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dashboard/Services/DatabaseService.FinOps.cs` around lines 2556 - 2561, The
GrowthDisplay getter treats percent-growth files with a configured growth of 0
as "0%" but should show "Disabled" to match fixed-growth behavior; update the
IsPercentGrowth true branch in GrowthDisplay to check for GrowthPct.HasValue &&
GrowthPct == 0 and return "Disabled" in that case, otherwise format the
percentage (e.g., $"{GrowthPct}%"); keep the null case returning "-" and the
false branch unchanged (AutoGrowthMb == 0 => "Disabled" else formatted MB).


public decimal AutoGrowthSort => IsPercentGrowth switch
{
null => -1m,
true => (decimal)(GrowthPct ?? -1),
false => AutoGrowthMb
};

public string VlfCountDisplay => string.Equals(FileTypeDesc, "LOG", StringComparison.OrdinalIgnoreCase)
? (VlfCount?.ToString() ?? "-") : "N/A";

public int VlfCountSort => string.Equals(FileTypeDesc, "LOG", StringComparison.OrdinalIgnoreCase)
? (VlfCount ?? 0) : -1;
}

public class FinOpsTopResourceConsumer
Expand Down
4 changes: 2 additions & 2 deletions Dashboard/Services/DatabaseService.QueryPerformance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1014,8 +1014,8 @@ USE HINT('ENABLE_PARALLEL_PLAN_PREFERENCE')
AvgRows = reader.IsDBNull(24) ? null : reader.GetInt64(24),
MinRows = reader.IsDBNull(25) ? null : reader.GetInt64(25),
MaxRows = reader.IsDBNull(26) ? null : reader.GetInt64(26),
MinDop = reader.IsDBNull(27) ? null : reader.GetInt16(27),
MaxDop = reader.IsDBNull(28) ? null : reader.GetInt16(28),
MinDop = reader.IsDBNull(27) ? null : Convert.ToInt16(reader.GetValue(27)),
MaxDop = reader.IsDBNull(28) ? null : Convert.ToInt16(reader.GetValue(28)),
MinGrantKb = reader.IsDBNull(29) ? null : reader.GetInt64(29),
MaxGrantKb = reader.IsDBNull(30) ? null : reader.GetInt64(30),
TotalSpills = reader.IsDBNull(31) ? 0 : reader.GetInt64(31),
Expand Down
2 changes: 1 addition & 1 deletion Dashboard/Services/DatabaseService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ CASE health_status
FailureRatePercent = reader.IsDBNull(4) ? 0m : Convert.ToDecimal(reader.GetValue(4), CultureInfo.InvariantCulture),
TotalRuns7d = reader.IsDBNull(5) ? 0L : Convert.ToInt64(reader.GetValue(5), CultureInfo.InvariantCulture),
FailedRuns7d = reader.IsDBNull(6) ? 0L : Convert.ToInt64(reader.GetValue(6), CultureInfo.InvariantCulture),
AvgDurationMs = reader.IsDBNull(7) ? 0 : Convert.ToInt32(reader.GetValue(7), CultureInfo.InvariantCulture),
AvgDurationMs = reader.IsDBNull(7) ? 0 : Convert.ToInt64(reader.GetValue(7), CultureInfo.InvariantCulture),
TotalRowsCollected7d = reader.IsDBNull(8) ? 0L : Convert.ToInt64(reader.GetValue(8), CultureInfo.InvariantCulture)
});
}
Expand Down
21 changes: 21 additions & 0 deletions Lite/Controls/FinOpsTab.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -1016,6 +1016,27 @@
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding GrowthDisplay}" Width="110" SortMemberPath="AutoGrowthSort">
<DataGridTextColumn.Header>
<StackPanel Orientation="Horizontal">
<Button Style="{DynamicResource ColumnFilterButtonStyle}" Tag="GrowthDisplay" Click="FilterButton_Click" Margin="0,0,4,0"/>
<TextBlock Text="Auto Growth" FontWeight="Bold" VerticalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding VlfCountDisplay}" Width="80" SortMemberPath="VlfCountSort">
<DataGridTextColumn.Header>
<StackPanel Orientation="Horizontal">
<Button Style="{DynamicResource ColumnFilterButtonStyle}" Tag="VlfCountDisplay" Click="FilterButton_Click" Margin="0,0,4,0"/>
<TextBlock Text="VLF Count" FontWeight="Bold" VerticalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Right"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Monthly Cost ($)" Binding="{Binding MonthlyCostShare, StringFormat='{}{0:N2}'}" Width="110">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
Expand Down
Loading
Loading