<UserControl x:Class="LogAnalyzerTool.View.UploadView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:LogAnalyzerTool.View" xmlns:vm="clr-namespace:LogAnalyzerTool.ViewModel" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> <UserControl.DataContext> <vm:UploadViewModel /> </UserControl.DataContext>

text

<Grid Margin="10"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> text

<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,0,0,10" Grid.Row="0"> <TextBlock Text="{Binding SelectedFileName}" VerticalAlignment="Center" Margin="0,0,10,0" /> <Button Content="📂 Browse" Command="{Binding BrowseCommand}" Margin="0,0,10,0" /> <Button Content="Upload" Command="{Binding UploadCommand}" /> </StackPanel> <DataGrid ItemsSource="{Binding LogTable}" AutoGenerateColumns="False" HeadersVisibility="Column" GridLinesVisibility="All" IsReadOnly="True" CanUserAddRows="False" Grid.Row="1"> <DataGrid.Columns> <DataGridTextColumn Header="Log ID" Binding="{Binding Path=LogId}"/> <DataGridTextColumn Header="Workflow" Binding="{Binding Path=WorkflowName}"/> <DataGridTextColumn Header="Operation" Binding="{Binding Path=OperationName}"/> <DataGridTextColumn Header="Expected Log" Binding="{Binding Path=ExpectedLog}" Width="5*"/> <DataGridTextColumn Header="Is Matching" Binding="{Binding Path=IsMatching}"/> </DataGrid.Columns> </DataGrid> </Grid> </UserControl> using LogAnalyzerTool.Commands; using LogAnalyzerTool.Model; using Microsoft.Win32; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Data; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Windows.Input; namespace LogAnalyzerTool.ViewModel { public class UploadViewModel : ViewModelBase {

text

text

private string _selectedFileName; public string SelectedFileName { get => _selectedFileName; set => SetProperty(ref _selectedFileName, value); }

private DataTable _logTable; public DataTable LogTable { get => _logTable; set => SetProperty(ref _logTable, value); }

public ICommand BrowseCommand { get; } public ICommand UploadCommand { get; }

public UploadViewModel() { LogTable = CreateLogDataTable(); BrowseCommand = new RelayCommand(ExecuteBrowse); UploadCommand = new RelayCommand(ExecuteUpload); }

private DataTable CreateLogDataTable() { DataTable dt = new DataTable(); dt.Columns.Add("WorkflowId", typeof(int)); dt.Columns.Add("WorkflowName", typeof(string)); dt.Columns.Add("OperationId", typeof(int)); dt.Columns.Add("OperationName", typeof(string)); dt.Columns.Add("LogId", typeof(int)); dt.Columns.Add("ExpectedLog", typeof(string)); dt.Columns.Add("IsMatching", typeof(string));

text

dt.Rows.Add(1, "Scanning", 1, "Add ExamCard", 1, "ExamCardsUI::ECDatabaseBrowser(UserEvent): (Action) OnRowDoubleClicked", "Pending"); dt.Rows.Add(1, "Scanning", 1, "Add ExamCard", 2, "ExamCardsUI::ECDatabaseBrowser(UserEvent): (Action) OnAddToExecutionList", "Pending"); dt.Rows.Add(1, "Scanning", 1, "Add ExamCard", 3, "ExamCards(ExecutionListLogic): ProcessSelection enter for: Philips.PmsMR.ExamCards.ExamCardsUI.ECDatabaseBrowser.", "Pending");

return dt; }

private void ExecuteBrowse(object parameter) { OpenFileDialog openFileDialog = new OpenFileDialog(); openFileDialog.Filter = "Log files (.log)|.log|All files (.)|.";

text

if (openFileDialog.ShowDialog() == true) { SelectedFileName = openFileDialog.FileName; } }

private void ExecuteUpload(object parameter) { if (string.IsNullOrEmpty(SelectedFileName) || !File.Exists(SelectedFileName)) return;

text

string[] lines = File.ReadAllLines(SelectedFileName); bool isMatch = CheckLinesAppearInOrder(lines, _expectedLogs);

for (int i = 0; i < LogTable.Rows.Count; i++) { DataRow row = LogTable.Rows[i]; string expectedLog = row["ExpectedLog"].ToString(); if (_expectedLogs.Contains(expectedLog)) { row["IsMatching"] = isMatch ? "Yes" : "No"; } } }

private readonly List<string> _expectedLogs = new() { "ExamCardsUI::ECDatabaseBrowser(UserEvent): (Action) OnRowDoubleClicked", "ExamCardsUI::ECDatabaseBrowser(UserEvent): (Action) OnAddToExecutionList", "ExamCards(ExecutionListLogic): ProcessSelection enter for: Philips.PmsMR.ExamCards.ExamCardsUI.ECDatabaseBrowser." };

private bool CheckLinesAppearInOrder(string[] lines, List<string> patterns) { int index = 0;

text

foreach (var line in lines) { if (line.Contains(patterns[index])) { index++; if (index == patterns.Count) return true; } }

return false; } } }

Look at above code in c# wpf. I want to make my datagrid expandable and collapsible. There are multiple workflows, each should be expandable. Inside each workflow, there will be multiple operations. Each operation should be expandable, showing the logs and ismatching status. Suggest an existing control for this or make changes to the existing datagrid.

public class ExpectedLog : ViewModelBase { public string LogName { get; set; }

text

private string _isMatching; public string IsMatching { get => _isMatching; set { _isMatching = value; OnPropertyChanged(nameof(IsMatching)); // Notify the UI that this property has changed } } }

namespace LogAnalyzerTool.Model { public class Operation { public string OperationName { get; set; } public ObservableCollection<ExpectedLog> ExpectedLogs { get; set; } = new(); }

}

namespace LogAnalyzerTool.Model { public class Workflow { public string WorkflowName { get; set; } public ObservableCollection<Operation> Operations { get; set; } = new(); }

}

These are some classes used in my project. Answer keeping these in mind. Use datagrid with Custom Row Template with Expander: