Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
10 changes: 10 additions & 0 deletions Dashboard/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,16 @@ protected override void OnExit(ExitEventArgs e)
private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
var exception = e.ExceptionObject as Exception;

/* Silently swallow Hardcodet TrayToolTip race condition (issue #422) when it
escapes the Dispatcher path — happens during tray-Exit shutdown when the
Dispatcher's exception hooks are torn down before the tray library finishes. */
if (exception != null && IsTrayToolTipCrash(exception))
{
Logger.Warning("Suppressed Hardcodet TrayToolTip crash (issue #422) in AppDomain handler");
return;
}

Logger.Fatal("Unhandled AppDomain Exception", exception ?? new Exception("Unknown exception"));

if (e.IsTerminating)
Expand Down
149 changes: 149 additions & 0 deletions Dashboard/Controls/MemoryContent.CopyExport.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* 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.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using Microsoft.Win32;
using PerformanceMonitorDashboard.Helpers;
using PerformanceMonitorDashboard.Models;
using PerformanceMonitorDashboard.Services;

namespace PerformanceMonitorDashboard.Controls
{
public partial class MemoryContent : UserControl
{
#region Context Menu Handlers

private void CopyCell_Click(object sender, RoutedEventArgs e)
{
if (sender is MenuItem menuItem && menuItem.Parent is ContextMenu contextMenu)
{
var dataGrid = TabHelpers.FindDataGridFromContextMenu(contextMenu);
if (dataGrid != null && dataGrid.CurrentCell.Item != null)
{
var cellContent = TabHelpers.GetCellContent(dataGrid, dataGrid.CurrentCell);
if (!string.IsNullOrEmpty(cellContent))
{
/* Use SetDataObject with copy=false to avoid WPF's problematic Clipboard.Flush() */
Clipboard.SetDataObject(cellContent, false);
}
}
}
}

private void CopyRow_Click(object sender, RoutedEventArgs e)
{
if (sender is MenuItem menuItem && menuItem.Parent is ContextMenu contextMenu)
{
var dataGrid = TabHelpers.FindDataGridFromContextMenu(contextMenu);
if (dataGrid != null && dataGrid.SelectedItem != null)
{
var rowText = TabHelpers.GetRowAsText(dataGrid, dataGrid.SelectedItem);
/* Use SetDataObject with copy=false to avoid WPF's problematic Clipboard.Flush() */
Clipboard.SetDataObject(rowText, false);
}
}
}

private void CopyAllRows_Click(object sender, RoutedEventArgs e)
{
if (sender is MenuItem menuItem && menuItem.Parent is ContextMenu contextMenu)
{
var dataGrid = TabHelpers.FindDataGridFromContextMenu(contextMenu);
if (dataGrid != null && dataGrid.Items.Count > 0)
{
var sb = new StringBuilder();

// Add headers
var headers = new List<string>();
foreach (var column in dataGrid.Columns)
{
if (column is DataGridBoundColumn)
{
headers.Add(Helpers.DataGridClipboardBehavior.GetHeaderText(column));
}
}
sb.AppendLine(string.Join("\t", headers));

// Add all rows
foreach (var item in dataGrid.Items)
{
sb.AppendLine(TabHelpers.GetRowAsText(dataGrid, item));
}

/* Use SetDataObject with copy=false to avoid WPF's problematic Clipboard.Flush() */
Clipboard.SetDataObject(sb.ToString(), false);
}
}
}

private void ExportToCsv_Click(object sender, RoutedEventArgs e)
{
if (sender is MenuItem menuItem && menuItem.Parent is ContextMenu contextMenu)
{
var dataGrid = TabHelpers.FindDataGridFromContextMenu(contextMenu);
if (dataGrid != null && dataGrid.Items.Count > 0)
{
string prefix = "memory";


var saveFileDialog = new SaveFileDialog
{
FileName = $"{prefix}_{DateTime.Now:yyyyMMdd_HHmmss}.csv",
DefaultExt = ".csv",
Filter = "CSV Files (*.csv)|*.csv|All Files (*.*)|*.*"
};

if (saveFileDialog.ShowDialog() == true)
{
try
{
var sb = new StringBuilder();

// Add headers
var headers = new List<string>();
foreach (var column in dataGrid.Columns)
{
if (column is DataGridBoundColumn)
{
headers.Add(TabHelpers.EscapeCsvField(Helpers.DataGridClipboardBehavior.GetHeaderText(column), TabHelpers.CsvSeparator));
}
}
sb.AppendLine(string.Join(TabHelpers.CsvSeparator, headers));

// Add all rows
foreach (var item in dataGrid.Items)
{
var values = TabHelpers.GetRowValues(dataGrid, item);
sb.AppendLine(string.Join(TabHelpers.CsvSeparator, values.Select(v => TabHelpers.EscapeCsvField(v, TabHelpers.CsvSeparator))));
}

File.WriteAllText(saveFileDialog.FileName, sb.ToString());
MessageBox.Show($"Data exported successfully to:\n{saveFileDialog.FileName}", "Export Complete", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
MessageBox.Show($"Error exporting data:\n\n{ex.Message}", "Export Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
}
}

#endregion
}
}
Loading
Loading