Compare commits
3 Commits
ea96ba2bb9
...
376f7f6cf6
Author | SHA1 | Date |
---|---|---|
|
376f7f6cf6 | |
|
da9af34ded | |
|
38c54ab40b |
18
App.xaml
|
@ -1,11 +1,17 @@
|
||||||
<Application x:Class="S7Explorer.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
<Application x:Class="S7Explorer.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:S7Explorer"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:S7Explorer"
|
||||||
StartupUri="Views/MainWindow.xaml">
|
StartupUri="MainWindow.xaml">
|
||||||
<Application.Resources>
|
<Application.Resources>
|
||||||
<ResourceDictionary>
|
<!-- Define the application resources -->
|
||||||
<ResourceDictionary.MergedDictionaries>
|
<Style x:Key="ToolbarButtonStyle" TargetType="Button">
|
||||||
<!-- Aquí puedes incluir estilos globales -->
|
<Setter Property="Margin" Value="5" />
|
||||||
</ResourceDictionary.MergedDictionaries>
|
<Setter Property="Padding" Value="8,3" />
|
||||||
</ResourceDictionary>
|
<Setter Property="MinWidth" Value="80" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="StatusTextStyle" TargetType="TextBlock">
|
||||||
|
<Setter Property="Margin" Value="5" />
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center" />
|
||||||
|
</Style>
|
||||||
</Application.Resources>
|
</Application.Resources>
|
||||||
</Application>
|
</Application>
|
82
App.xaml.cs
|
@ -1,88 +1,8 @@
|
||||||
using System;
|
using System.Windows;
|
||||||
using System.IO;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Media.Imaging;
|
|
||||||
|
|
||||||
namespace S7Explorer
|
namespace S7Explorer
|
||||||
{
|
{
|
||||||
public partial class App : Application
|
public partial class App : Application
|
||||||
{
|
{
|
||||||
protected override void OnStartup(StartupEventArgs e)
|
|
||||||
{
|
|
||||||
base.OnStartup(e);
|
|
||||||
|
|
||||||
// Asegurarnos de que existe la carpeta Resources para los íconos
|
|
||||||
EnsureResourcesExist();
|
|
||||||
|
|
||||||
// Configurar manejo de excepciones no controladas
|
|
||||||
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
|
||||||
Current.DispatcherUnhandledException += Current_DispatcherUnhandledException;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EnsureResourcesExist()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Directorio de recursos
|
|
||||||
string resourcesDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources");
|
|
||||||
if (!Directory.Exists(resourcesDir))
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(resourcesDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Crear íconos básicos si no existen
|
|
||||||
CreateDefaultIconIfNotExists(resourcesDir, "project.png");
|
|
||||||
CreateDefaultIconIfNotExists(resourcesDir, "device.png");
|
|
||||||
CreateDefaultIconIfNotExists(resourcesDir, "folder.png");
|
|
||||||
CreateDefaultIconIfNotExists(resourcesDir, "db.png");
|
|
||||||
CreateDefaultIconIfNotExists(resourcesDir, "fb.png");
|
|
||||||
CreateDefaultIconIfNotExists(resourcesDir, "fc.png");
|
|
||||||
CreateDefaultIconIfNotExists(resourcesDir, "ob.png");
|
|
||||||
CreateDefaultIconIfNotExists(resourcesDir, "symbol.png");
|
|
||||||
CreateDefaultIconIfNotExists(resourcesDir, "default.png");
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
// Ignorar errores en la creación de recursos - no son críticos
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CreateDefaultIconIfNotExists(string directory, string filename)
|
|
||||||
{
|
|
||||||
string filePath = Path.Combine(directory, filename);
|
|
||||||
if (!File.Exists(filePath))
|
|
||||||
{
|
|
||||||
// Crear un ícono simple - en una aplicación real, incluirías recursos reales
|
|
||||||
BitmapSource bmp = BitmapSource.Create(16, 16, 96, 96, System.Windows.Media.PixelFormats.Bgr32, null, new byte[16 * 16 * 4], 16 * 4);
|
|
||||||
|
|
||||||
using (var fileStream = new FileStream(filePath, FileMode.Create))
|
|
||||||
{
|
|
||||||
BitmapEncoder encoder = new PngBitmapEncoder();
|
|
||||||
encoder.Frames.Add(BitmapFrame.Create(bmp));
|
|
||||||
encoder.Save(fileStream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
|
||||||
{
|
|
||||||
HandleException(e.ExceptionObject as Exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Current_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
|
|
||||||
{
|
|
||||||
HandleException(e.Exception);
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleException(Exception ex)
|
|
||||||
{
|
|
||||||
if (ex == null) return;
|
|
||||||
|
|
||||||
MessageBox.Show($"Ha ocurrido un error inesperado: {ex.Message}\n\nDetalles: {ex.StackTrace}",
|
|
||||||
"Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
||||||
|
|
||||||
// Aquí podrías añadir registro de errores
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,143 @@
|
||||||
|
using S7Explorer.Models;
|
||||||
|
using S7Explorer.Services;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace S7Explorer.Helpers
|
||||||
|
{
|
||||||
|
public class SearchHelper
|
||||||
|
{
|
||||||
|
private readonly LogService _logService;
|
||||||
|
|
||||||
|
public SearchHelper()
|
||||||
|
{
|
||||||
|
_logService = LogService.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<SearchResult> Search(ProjectStructure project, string searchText, bool caseSensitive, bool useRegex)
|
||||||
|
{
|
||||||
|
_logService.LogInfo($"Searching for '{searchText}' (Case sensitive: {caseSensitive}, Regex: {useRegex})");
|
||||||
|
|
||||||
|
List<SearchResult> results = new List<SearchResult>();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(searchText))
|
||||||
|
return results;
|
||||||
|
|
||||||
|
// Prepare regex if needed
|
||||||
|
Regex? regex = null;
|
||||||
|
if (useRegex)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
RegexOptions options = caseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase;
|
||||||
|
regex = new Regex(searchText, options);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logService.LogError($"Invalid regex pattern: {ex.Message}");
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search blocks
|
||||||
|
SearchProjectItem(project.BlocksFolder, results, searchText, caseSensitive, regex);
|
||||||
|
|
||||||
|
// Search symbols
|
||||||
|
SearchProjectItem(project.SymbolsFolder, results, searchText, caseSensitive, regex);
|
||||||
|
|
||||||
|
// Search hardware
|
||||||
|
SearchProjectItem(project.HardwareFolder, results, searchText, caseSensitive, regex);
|
||||||
|
|
||||||
|
_logService.LogInfo($"Found {results.Count} results");
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logService.LogError($"Error during search: {ex.Message}");
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SearchProjectItem(ProjectItem item, List<SearchResult> results, string searchText,
|
||||||
|
bool caseSensitive, Regex? regex)
|
||||||
|
{
|
||||||
|
// Check name
|
||||||
|
bool nameMatch = MatchesSearch(item.Name, searchText, caseSensitive, regex);
|
||||||
|
|
||||||
|
// For blocks, check content and comments
|
||||||
|
bool contentMatch = false;
|
||||||
|
if (item is BlockItem blockItem)
|
||||||
|
{
|
||||||
|
contentMatch = MatchesSearch(blockItem.BlockContent, searchText, caseSensitive, regex) ||
|
||||||
|
MatchesSearch(blockItem.BlockComment, searchText, caseSensitive, regex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For symbols, check address, type, and comment
|
||||||
|
else if (item is SymbolItem symbolItem)
|
||||||
|
{
|
||||||
|
contentMatch = MatchesSearch(symbolItem.SymbolAddress, searchText, caseSensitive, regex) ||
|
||||||
|
MatchesSearch(symbolItem.SymbolDataType, searchText, caseSensitive, regex) ||
|
||||||
|
MatchesSearch(symbolItem.SymbolComment, searchText, caseSensitive, regex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For hardware, check module info
|
||||||
|
else if (item is HardwareItem hardwareItem)
|
||||||
|
{
|
||||||
|
contentMatch = MatchesSearch(hardwareItem.ModuleType, searchText, caseSensitive, regex) ||
|
||||||
|
MatchesSearch(hardwareItem.OrderNumber, searchText, caseSensitive, regex) ||
|
||||||
|
MatchesSearch(hardwareItem.Position, searchText, caseSensitive, regex) ||
|
||||||
|
MatchesSearch(hardwareItem.Address, searchText, caseSensitive, regex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nameMatch || contentMatch)
|
||||||
|
{
|
||||||
|
results.Add(new SearchResult
|
||||||
|
{
|
||||||
|
Item = item,
|
||||||
|
MatchType = nameMatch ? (contentMatch ? MatchType.Both : MatchType.Name) : MatchType.Content
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search children recursively
|
||||||
|
foreach (var child in item.Children)
|
||||||
|
{
|
||||||
|
SearchProjectItem(child, results, searchText, caseSensitive, regex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool MatchesSearch(string text, string searchText, bool caseSensitive, Regex? regex)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(text))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (regex != null)
|
||||||
|
return regex.IsMatch(text);
|
||||||
|
|
||||||
|
return caseSensitive
|
||||||
|
? text.Contains(searchText)
|
||||||
|
: text.ToLower().Contains(searchText.ToLower());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SearchResult
|
||||||
|
{
|
||||||
|
public ProjectItem Item { get; set; } = null!;
|
||||||
|
public MatchType MatchType { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{Item.Name} - {MatchType}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum MatchType
|
||||||
|
{
|
||||||
|
Name,
|
||||||
|
Content,
|
||||||
|
Both
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
using S7Explorer.Models;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace S7Explorer.Helpers
|
||||||
|
{
|
||||||
|
public static class TreeViewHelper
|
||||||
|
{
|
||||||
|
public static ProjectItem? FindItemByPath(ObservableCollection<ProjectItem> items, string path)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(path))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
string[] parts = path.Split('/');
|
||||||
|
return FindItemByParts(items, parts, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ProjectItem? FindItemByParts(IEnumerable<ProjectItem> items, string[] parts, int level)
|
||||||
|
{
|
||||||
|
if (level >= parts.Length)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var matchingItem = items.FirstOrDefault(i => i.Name == parts[level]);
|
||||||
|
if (matchingItem == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (level == parts.Length - 1)
|
||||||
|
return matchingItem;
|
||||||
|
|
||||||
|
return FindItemByParts(matchingItem.Children, parts, level + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ProjectItem? FindItemByName(ObservableCollection<ProjectItem> items, string name)
|
||||||
|
{
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
if (item.Name == name)
|
||||||
|
return item;
|
||||||
|
|
||||||
|
var found = FindItemByName(item.Children, name);
|
||||||
|
if (found != null)
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<ProjectItem> FindItemsByText(ObservableCollection<ProjectItem> items, string text)
|
||||||
|
{
|
||||||
|
List<ProjectItem> results = new List<ProjectItem>();
|
||||||
|
FindItemsByTextRecursive(items, text.ToLower(), results);
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void FindItemsByTextRecursive(IEnumerable<ProjectItem> items, string text, List<ProjectItem> results)
|
||||||
|
{
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
if (item.Name.ToLower().Contains(text))
|
||||||
|
results.Add(item);
|
||||||
|
|
||||||
|
FindItemsByTextRecursive(item.Children, text, results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ExpandToItem(ProjectItem item)
|
||||||
|
{
|
||||||
|
// Expand all parent items to make the item visible
|
||||||
|
ProjectItem? current = item.Parent;
|
||||||
|
while (current != null)
|
||||||
|
{
|
||||||
|
current.IsExpanded = true;
|
||||||
|
current = current.Parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ExpandAll(ProjectItem item)
|
||||||
|
{
|
||||||
|
item.IsExpanded = true;
|
||||||
|
|
||||||
|
foreach (var child in item.Children)
|
||||||
|
{
|
||||||
|
ExpandAll(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CollapseAll(ProjectItem item)
|
||||||
|
{
|
||||||
|
item.IsExpanded = false;
|
||||||
|
|
||||||
|
foreach (var child in item.Children)
|
||||||
|
{
|
||||||
|
CollapseAll(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,69 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace S7Explorer
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Extensiones LINQ para funcionalidades específicas de la aplicación
|
|
||||||
/// </summary>
|
|
||||||
public static class LinqExtensions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Cuenta elementos en una colección que cumplen un predicado
|
|
||||||
/// </summary>
|
|
||||||
public static int Count<T>(this IEnumerable<T> source, Func<T, bool> predicate)
|
|
||||||
{
|
|
||||||
if (source == null)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return source.Where(predicate).Count();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Verifica si una colección contiene al menos un elemento
|
|
||||||
/// </summary>
|
|
||||||
public static bool Any<T>(this IEnumerable<T> source)
|
|
||||||
{
|
|
||||||
if (source == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return source.Any(item => true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Verifica si una colección contiene al menos un elemento que cumpla un predicado
|
|
||||||
/// </summary>
|
|
||||||
public static bool Any<T>(this IEnumerable<T> source, Func<T, bool> predicate)
|
|
||||||
{
|
|
||||||
if (source == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
using (var enumerator = source.GetEnumerator())
|
|
||||||
{
|
|
||||||
while (enumerator.MoveNext())
|
|
||||||
{
|
|
||||||
if (predicate(enumerator.Current))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Selecciona elementos de una colección que cumplen un predicado
|
|
||||||
/// </summary>
|
|
||||||
public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, bool> predicate)
|
|
||||||
{
|
|
||||||
if (source == null)
|
|
||||||
yield break;
|
|
||||||
|
|
||||||
foreach (var item in source)
|
|
||||||
{
|
|
||||||
if (predicate(item))
|
|
||||||
yield return item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
<Window x:Class="S7Explorer.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:S7Explorer"
|
||||||
|
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit" mc:Ignorable="d" Title="S7 Project Explorer" Height="700"
|
||||||
|
Width="1000" WindowStartupLocation="CenterScreen">
|
||||||
|
|
||||||
|
<Window.Resources>
|
||||||
|
<!-- Icon Converter -->
|
||||||
|
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
|
||||||
|
|
||||||
|
<!-- HierarchicalDataTemplate for TreeView Items -->
|
||||||
|
<HierarchicalDataTemplate x:Key="ProjectItemTemplate" ItemsSource="{Binding Children}">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBlock Text="{Binding Name}" Margin="5,0,0,0" />
|
||||||
|
</StackPanel>
|
||||||
|
</HierarchicalDataTemplate>
|
||||||
|
|
||||||
|
<!-- Log Entry Template -->
|
||||||
|
<DataTemplate x:Key="LogEntryTemplate">
|
||||||
|
<TextBlock Text="{Binding}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</Window.Resources>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="120" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- Toolbar -->
|
||||||
|
<ToolBar Grid.Row="0">
|
||||||
|
<Button Content="Load Project" Command="{Binding LoadProjectCommand}"
|
||||||
|
ToolTip="Load a Siemens S7 project file (.s7p)" Style="{StaticResource ToolbarButtonStyle}" />
|
||||||
|
<Button Content="Reload" Command="{Binding ReloadProjectCommand}" ToolTip="Reload the current project"
|
||||||
|
Style="{StaticResource ToolbarButtonStyle}" IsEnabled="{Binding IsProjectLoaded}" />
|
||||||
|
<Separator />
|
||||||
|
<Button Content="Export" Command="{Binding ExportDocumentationCommand}"
|
||||||
|
ToolTip="Export project documentation to a file" Style="{StaticResource ToolbarButtonStyle}"
|
||||||
|
IsEnabled="{Binding IsProjectLoaded}" />
|
||||||
|
<Separator />
|
||||||
|
<Label Content="Search:" VerticalAlignment="Center" />
|
||||||
|
<TextBox Width="200" Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
VerticalAlignment="Center" Margin="5" />
|
||||||
|
<Button Content="Find" Command="{Binding SearchCommand}" ToolTip="Search in project"
|
||||||
|
Style="{StaticResource ToolbarButtonStyle}" IsEnabled="{Binding IsProjectLoaded}" />
|
||||||
|
<CheckBox Content="Case Sensitive" VerticalAlignment="Center" Margin="5"
|
||||||
|
IsChecked="{Binding IsCaseSensitive}" />
|
||||||
|
<CheckBox Content="Regex" VerticalAlignment="Center" Margin="5" IsChecked="{Binding UseRegex}" />
|
||||||
|
</ToolBar>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<Grid Grid.Row="1">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="350" />
|
||||||
|
<ColumnDefinition Width="5" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- Project Explorer Tree -->
|
||||||
|
<DockPanel Grid.Column="0">
|
||||||
|
<ToolBar DockPanel.Dock="Top">
|
||||||
|
<Button Content="Expand All" Command="{Binding ExpandAllCommand}" ToolTip="Expand all tree nodes"
|
||||||
|
Style="{StaticResource ToolbarButtonStyle}" IsEnabled="{Binding IsProjectLoaded}" />
|
||||||
|
<Button Content="Collapse All" Command="{Binding CollapseAllCommand}"
|
||||||
|
ToolTip="Collapse all tree nodes" Style="{StaticResource ToolbarButtonStyle}"
|
||||||
|
IsEnabled="{Binding IsProjectLoaded}" />
|
||||||
|
</ToolBar>
|
||||||
|
|
||||||
|
<TreeView ItemsSource="{Binding ProjectStructure}" ItemTemplate="{StaticResource ProjectItemTemplate}"
|
||||||
|
SelectedItemChanged="TreeView_SelectedItemChanged" VirtualizingPanel.IsVirtualizing="True"
|
||||||
|
VirtualizingPanel.VirtualizationMode="Recycling">
|
||||||
|
</TreeView>
|
||||||
|
</DockPanel>
|
||||||
|
|
||||||
|
<!-- Splitter -->
|
||||||
|
<GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Stretch" />
|
||||||
|
|
||||||
|
<!-- Property Grid -->
|
||||||
|
<xctk:PropertyGrid Grid.Column="2" SelectedObject="{Binding SelectedItemDetails}"
|
||||||
|
AutoGenerateProperties="True" IsReadOnly="True" ShowSearchBox="True" ShowSortOptions="True"
|
||||||
|
ShowTitle="True" ShowAdvancedOptions="False" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Status Bar -->
|
||||||
|
<StatusBar Grid.Row="2">
|
||||||
|
<StatusBarItem>
|
||||||
|
<TextBlock Text="{Binding ProjectPath}" Style="{StaticResource StatusTextStyle}" />
|
||||||
|
</StatusBarItem>
|
||||||
|
<Separator />
|
||||||
|
<StatusBarItem>
|
||||||
|
<TextBlock Text="{Binding SelectedItem.Name, StringFormat=Selected: {0}}"
|
||||||
|
Style="{StaticResource StatusTextStyle}" />
|
||||||
|
</StatusBarItem>
|
||||||
|
<StatusBarItem HorizontalAlignment="Right">
|
||||||
|
<ProgressBar Width="100" Height="15" IsIndeterminate="{Binding IsLoading}"
|
||||||
|
Visibility="{Binding IsLoading, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
||||||
|
</StatusBarItem>
|
||||||
|
</StatusBar>
|
||||||
|
|
||||||
|
<!-- Log View -->
|
||||||
|
<DockPanel Grid.Row="3">
|
||||||
|
<ToolBar DockPanel.Dock="Top">
|
||||||
|
<Label Content="Log:" VerticalAlignment="Center" />
|
||||||
|
<Button Content="Clear" Command="{Binding ClearLogCommand}" ToolTip="Clear log messages"
|
||||||
|
Style="{StaticResource ToolbarButtonStyle}" />
|
||||||
|
</ToolBar>
|
||||||
|
|
||||||
|
<ListView ItemsSource="{Binding LogEntries}" ItemTemplate="{StaticResource LogEntryTemplate}"
|
||||||
|
ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollBarVisibility="Auto"
|
||||||
|
VirtualizingPanel.IsVirtualizing="True" VirtualizingPanel.VirtualizationMode="Recycling">
|
||||||
|
</ListView>
|
||||||
|
</DockPanel>
|
||||||
|
|
||||||
|
<!-- Loading Overlay -->
|
||||||
|
<Grid Grid.RowSpan="4" Visibility="{Binding IsLoading, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||||
|
<Rectangle Fill="Black" Opacity="0.3" />
|
||||||
|
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||||
|
<TextBlock Text="Loading..." FontSize="20" Foreground="White" />
|
||||||
|
<ProgressBar Width="200" Height="20" IsIndeterminate="True" Margin="0,10,0,0" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
|
@ -0,0 +1,96 @@
|
||||||
|
using S7Explorer.Models;
|
||||||
|
using S7Explorer.Services;
|
||||||
|
using S7Explorer.ViewModels;
|
||||||
|
using System;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace S7Explorer
|
||||||
|
{
|
||||||
|
public enum LogLevel
|
||||||
|
{
|
||||||
|
Debug,
|
||||||
|
Info,
|
||||||
|
Warning,
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LogEntry
|
||||||
|
{
|
||||||
|
public DateTime Timestamp { get; set; }
|
||||||
|
public LogLevel Level { get; set; }
|
||||||
|
public string Message { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"[{Timestamp:yyyy-MM-dd HH:mm:ss}] [{Level}] {Message}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class MainWindow : Window
|
||||||
|
{
|
||||||
|
private MainViewModel ViewModel;
|
||||||
|
|
||||||
|
public MainWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
ViewModel = new MainViewModel();
|
||||||
|
DataContext = ViewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
|
||||||
|
{
|
||||||
|
if (e.NewValue is ProjectItem item)
|
||||||
|
{
|
||||||
|
ViewModel.SelectedItem = item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IconConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is string iconName)
|
||||||
|
{
|
||||||
|
// In a real application, return an actual image based on the icon name
|
||||||
|
// Here we're just returning a placeholder
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LogLevelConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is LogLevel level)
|
||||||
|
{
|
||||||
|
return level switch
|
||||||
|
{
|
||||||
|
LogLevel.Debug => Brushes.Gray,
|
||||||
|
LogLevel.Info => Brushes.Black,
|
||||||
|
LogLevel.Warning => Brushes.Orange,
|
||||||
|
LogLevel.Error => Brushes.Red,
|
||||||
|
_ => Brushes.Black
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return Brushes.Black;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
using System.Text;
|
||||||
|
using DotNetSiemensPLCToolBoxLibrary.DataTypes.Blocks;
|
||||||
|
using DotNetSiemensPLCToolBoxLibrary.DataTypes.Blocks.Step7V5;
|
||||||
|
|
||||||
|
namespace S7Explorer.Models
|
||||||
|
{
|
||||||
|
public class BlockItem : ProjectItem
|
||||||
|
{
|
||||||
|
public ProjectBlockInfo? BlockInfo { get; set; }
|
||||||
|
public S7Block? BlockData { get; set; }
|
||||||
|
public string BlockType { get; set; } = string.Empty;
|
||||||
|
public int BlockNumber { get; set; }
|
||||||
|
public string BlockComment { get; set; } = string.Empty;
|
||||||
|
public string BlockContent { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
// Override to provide block-specific object for property grid
|
||||||
|
public override object? GetDetailsObject()
|
||||||
|
{
|
||||||
|
return BlockData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override to provide the appropriate icon
|
||||||
|
public override string GetIcon()
|
||||||
|
{
|
||||||
|
return BlockType switch
|
||||||
|
{
|
||||||
|
"OB" => "OB",
|
||||||
|
"FC" => "FC",
|
||||||
|
"FB" => "FB",
|
||||||
|
"DB" => "DB",
|
||||||
|
"UDT" => "UDT",
|
||||||
|
"SFC" => "SFC",
|
||||||
|
"SFB" => "SFB",
|
||||||
|
_ => "Block"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate export text for documentation
|
||||||
|
public override string GetExportText()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.AppendLine($"Block: {Name}");
|
||||||
|
sb.AppendLine($"Type: {BlockType}{BlockNumber}");
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(BlockComment))
|
||||||
|
sb.AppendLine($"Comment: {BlockComment}");
|
||||||
|
|
||||||
|
if (BlockData != null)
|
||||||
|
{
|
||||||
|
sb.AppendLine($"Author: {BlockData.Author}");
|
||||||
|
sb.AppendLine($"Family: {BlockData.Family}");
|
||||||
|
sb.AppendLine($"Version: {BlockData.Version}");
|
||||||
|
sb.AppendLine($"Last Modified: {BlockData.LastCodeChange}");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine("// Block Content");
|
||||||
|
sb.AppendLine(BlockContent);
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
|
||||||
|
namespace S7Explorer.Models
|
||||||
|
{
|
||||||
|
public class ExportSettings : ObservableObject
|
||||||
|
{
|
||||||
|
private bool _exportBlocks = true;
|
||||||
|
private bool _exportSymbols = true;
|
||||||
|
private bool _exportHardware = true;
|
||||||
|
private bool _includeBlockCode = true;
|
||||||
|
private bool _includeComments = true;
|
||||||
|
private string _exportPath = string.Empty;
|
||||||
|
private ExportFormat _exportFormat = ExportFormat.PlainText;
|
||||||
|
|
||||||
|
public bool ExportBlocks
|
||||||
|
{
|
||||||
|
get => _exportBlocks;
|
||||||
|
set => SetProperty(ref _exportBlocks, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ExportSymbols
|
||||||
|
{
|
||||||
|
get => _exportSymbols;
|
||||||
|
set => SetProperty(ref _exportSymbols, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ExportHardware
|
||||||
|
{
|
||||||
|
get => _exportHardware;
|
||||||
|
set => SetProperty(ref _exportHardware, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IncludeBlockCode
|
||||||
|
{
|
||||||
|
get => _includeBlockCode;
|
||||||
|
set => SetProperty(ref _includeBlockCode, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IncludeComments
|
||||||
|
{
|
||||||
|
get => _includeComments;
|
||||||
|
set => SetProperty(ref _includeComments, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ExportPath
|
||||||
|
{
|
||||||
|
get => _exportPath;
|
||||||
|
set => SetProperty(ref _exportPath, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExportFormat ExportFormat
|
||||||
|
{
|
||||||
|
get => _exportFormat;
|
||||||
|
set => SetProperty(ref _exportFormat, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ExportFormat
|
||||||
|
{
|
||||||
|
PlainText,
|
||||||
|
MarkDown,
|
||||||
|
HTML,
|
||||||
|
JSON
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace S7Explorer.Models
|
||||||
|
{
|
||||||
|
public class HardwareItem : ProjectItem
|
||||||
|
{
|
||||||
|
public string ModuleType { get; set; } = string.Empty;
|
||||||
|
public string OrderNumber { get; set; } = string.Empty;
|
||||||
|
public string HardwareVersion { get; set; } = string.Empty;
|
||||||
|
public string FirmwareVersion { get; set; } = string.Empty;
|
||||||
|
public string Position { get; set; } = string.Empty;
|
||||||
|
public string Address { get; set; } = string.Empty;
|
||||||
|
public object? HardwareData { get; set; }
|
||||||
|
|
||||||
|
public override object? GetDetailsObject()
|
||||||
|
{
|
||||||
|
return HardwareData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string GetIcon()
|
||||||
|
{
|
||||||
|
return "Hardware";
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string GetExportText()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.AppendLine($"Hardware: {Name}");
|
||||||
|
sb.AppendLine($"Type: {ModuleType}");
|
||||||
|
sb.AppendLine($"Order Number: {OrderNumber}");
|
||||||
|
sb.AppendLine($"Hardware Version: {HardwareVersion}");
|
||||||
|
sb.AppendLine($"Firmware Version: {FirmwareVersion}");
|
||||||
|
sb.AppendLine($"Position: {Position}");
|
||||||
|
sb.AppendLine($"Address: {Address}");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
|
||||||
|
namespace S7Explorer.Models
|
||||||
|
{
|
||||||
|
// Base class for all project items
|
||||||
|
public class ProjectItem : ObservableObject
|
||||||
|
{
|
||||||
|
private string _name = string.Empty;
|
||||||
|
private string _path = string.Empty;
|
||||||
|
private ProjectItem? _parent;
|
||||||
|
private ObservableCollection<ProjectItem> _children = new();
|
||||||
|
private bool _isExpanded;
|
||||||
|
private bool _isSelected;
|
||||||
|
|
||||||
|
public string Name
|
||||||
|
{
|
||||||
|
get => _name;
|
||||||
|
set => SetProperty(ref _name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Path
|
||||||
|
{
|
||||||
|
get => _path;
|
||||||
|
set => SetProperty(ref _path, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProjectItem? Parent
|
||||||
|
{
|
||||||
|
get => _parent;
|
||||||
|
set => SetProperty(ref _parent, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableCollection<ProjectItem> Children
|
||||||
|
{
|
||||||
|
get => _children;
|
||||||
|
set => SetProperty(ref _children, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsExpanded
|
||||||
|
{
|
||||||
|
get => _isExpanded;
|
||||||
|
set => SetProperty(ref _isExpanded, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsSelected
|
||||||
|
{
|
||||||
|
get => _isSelected;
|
||||||
|
set => SetProperty(ref _isSelected, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual object? GetDetailsObject()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual string GetIcon()
|
||||||
|
{
|
||||||
|
return "FolderClosed";
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual string GetExportText()
|
||||||
|
{
|
||||||
|
return $"Item: {Name}\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
using DotNetSiemensPLCToolBoxLibrary.Projectfiles;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace S7Explorer.Models
|
||||||
|
{
|
||||||
|
public class ProjectStructure : ProjectItem
|
||||||
|
{
|
||||||
|
public Project? ProjectData { get; set; }
|
||||||
|
public string ProjectPath { get; set; } = string.Empty;
|
||||||
|
public string ProjectVersion { get; set; } = string.Empty;
|
||||||
|
public DateTime CreationDate { get; set; }
|
||||||
|
public DateTime LastModifiedDate { get; set; }
|
||||||
|
|
||||||
|
public ProjectItem BlocksFolder { get; } = new ProjectItem { Name = "Blocks" };
|
||||||
|
public ProjectItem SymbolsFolder { get; } = new ProjectItem { Name = "Symbols" };
|
||||||
|
public ProjectItem HardwareFolder { get; } = new ProjectItem { Name = "Hardware" };
|
||||||
|
|
||||||
|
public ProjectStructure()
|
||||||
|
{
|
||||||
|
Name = "Project";
|
||||||
|
Children.Add(BlocksFolder);
|
||||||
|
Children.Add(SymbolsFolder);
|
||||||
|
Children.Add(HardwareFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object? GetDetailsObject()
|
||||||
|
{
|
||||||
|
return new
|
||||||
|
{
|
||||||
|
ProjectPath,
|
||||||
|
ProjectVersion,
|
||||||
|
CreationDate,
|
||||||
|
LastModifiedDate
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string GetIcon()
|
||||||
|
{
|
||||||
|
return "Project";
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string GetExportText()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.AppendLine($"Project: {Name}");
|
||||||
|
sb.AppendLine($"Path: {ProjectPath}");
|
||||||
|
sb.AppendLine($"Version: {ProjectVersion}");
|
||||||
|
sb.AppendLine($"Created: {CreationDate}");
|
||||||
|
sb.AppendLine($"Last Modified: {LastModifiedDate}");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,150 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Linq;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace S7Explorer.Models
|
|
||||||
{
|
|
||||||
public class S7Block : S7Object
|
|
||||||
{
|
|
||||||
private string _authorName;
|
|
||||||
private string _family;
|
|
||||||
private string _version;
|
|
||||||
private DateTime? _modified;
|
|
||||||
private int _size;
|
|
||||||
private string _language;
|
|
||||||
|
|
||||||
[DisplayName("Autor")]
|
|
||||||
public string AuthorName
|
|
||||||
{
|
|
||||||
get => _authorName;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_authorName != value)
|
|
||||||
{
|
|
||||||
_authorName = value;
|
|
||||||
OnPropertyChanged(nameof(AuthorName));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[DisplayName("Familia")]
|
|
||||||
public string Family
|
|
||||||
{
|
|
||||||
get => _family;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_family != value)
|
|
||||||
{
|
|
||||||
_family = value;
|
|
||||||
OnPropertyChanged(nameof(Family));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[DisplayName("Versión")]
|
|
||||||
public string Version
|
|
||||||
{
|
|
||||||
get => _version;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_version != value)
|
|
||||||
{
|
|
||||||
_version = value;
|
|
||||||
OnPropertyChanged(nameof(Version));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[DisplayName("Modificado")]
|
|
||||||
public DateTime? Modified
|
|
||||||
{
|
|
||||||
get => _modified;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_modified != value)
|
|
||||||
{
|
|
||||||
_modified = value;
|
|
||||||
OnPropertyChanged(nameof(Modified));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[DisplayName("Tamaño (bytes)")]
|
|
||||||
public int Size
|
|
||||||
{
|
|
||||||
get => _size;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_size != value)
|
|
||||||
{
|
|
||||||
_size = value;
|
|
||||||
OnPropertyChanged(nameof(Size));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[DisplayName("Lenguaje")]
|
|
||||||
public string Language
|
|
||||||
{
|
|
||||||
get => _language;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_language != value)
|
|
||||||
{
|
|
||||||
_language = value;
|
|
||||||
OnPropertyChanged(nameof(Language));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public S7Block()
|
|
||||||
{
|
|
||||||
// Establece valores predeterminados específicos para bloques
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class S7DataBlock : S7Block
|
|
||||||
{
|
|
||||||
private bool _instanceDb;
|
|
||||||
|
|
||||||
[DisplayName("Es DB de Instancia")]
|
|
||||||
public bool IsInstanceDb
|
|
||||||
{
|
|
||||||
get => _instanceDb;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_instanceDb != value)
|
|
||||||
{
|
|
||||||
_instanceDb = value;
|
|
||||||
OnPropertyChanged(nameof(IsInstanceDb));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public S7DataBlock()
|
|
||||||
{
|
|
||||||
ObjectType = S7ObjectType.DataBlock;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class S7FunctionBlock : S7Block
|
|
||||||
{
|
|
||||||
public S7FunctionBlock()
|
|
||||||
{
|
|
||||||
ObjectType = S7ObjectType.FunctionBlock;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clase para representar un parámetro de función
|
|
||||||
/// </summary>
|
|
||||||
public class FunctionParameter
|
|
||||||
{
|
|
||||||
public string Name { get; set; }
|
|
||||||
public string DataType { get; set; }
|
|
||||||
public string Direction { get; set; } // IN, OUT, IN/OUT
|
|
||||||
public string Description { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,169 +1,49 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.Text;
|
||||||
using System.Linq;
|
using DotNetSiemensPLCToolBoxLibrary.DataTypes.Blocks.Step7V5;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using S7Explorer.Parsers;
|
|
||||||
|
|
||||||
namespace S7Explorer.Models
|
namespace S7Explorer.Models
|
||||||
{
|
{
|
||||||
public class S7Function : S7Block
|
public class S7Function
|
||||||
{
|
{
|
||||||
private string _returnType;
|
public S7Block Block { get; set; }
|
||||||
private List<FunctionParameter> _parameters;
|
public string Name { get; set; }
|
||||||
private string _interface;
|
public string Description { get; set; }
|
||||||
|
public List<S7FunctionParameter> Parameters { get; set; } = new List<S7FunctionParameter>();
|
||||||
[DisplayName("Tipo de Retorno")]
|
public string Code { get; set; }
|
||||||
[Description("Tipo de dato que retorna la función")]
|
|
||||||
[Category("Interfaz")]
|
|
||||||
public string ReturnType
|
|
||||||
{
|
|
||||||
get => _returnType;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_returnType != value)
|
|
||||||
{
|
|
||||||
_returnType = value;
|
|
||||||
OnPropertyChanged(nameof(ReturnType));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Browsable(false)]
|
|
||||||
[JsonIgnore]
|
|
||||||
public List<FunctionParameter> Parameters
|
|
||||||
{
|
|
||||||
get => _parameters;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_parameters != value)
|
|
||||||
{
|
|
||||||
_parameters = value;
|
|
||||||
OnPropertyChanged(nameof(Parameters));
|
|
||||||
|
|
||||||
// Actualizar la interfaz formateada cuando se cambian los parámetros
|
|
||||||
UpdateFormattedInterface();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[DisplayName("Interfaz")]
|
|
||||||
[Description("Interfaz de la función con sus parámetros")]
|
|
||||||
[Category("Interfaz")]
|
|
||||||
[EditorAttribute(typeof(System.ComponentModel.Design.MultilineStringEditor),
|
|
||||||
typeof(System.Drawing.Design.UITypeEditor))]
|
|
||||||
public string Interface
|
|
||||||
{
|
|
||||||
get => _interface;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_interface != value)
|
|
||||||
{
|
|
||||||
_interface = value;
|
|
||||||
OnPropertyChanged(nameof(Interface));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Propiedades adicionales específicas de FC
|
|
||||||
|
|
||||||
[DisplayName("Parámetros de Entrada")]
|
|
||||||
[Description("Número de parámetros de entrada")]
|
|
||||||
[Category("Estadísticas")]
|
|
||||||
public int InputParameterCount =>
|
|
||||||
Parameters?.Count(p => p.Direction == "IN") ?? 0;
|
|
||||||
|
|
||||||
[DisplayName("Parámetros de Salida")]
|
|
||||||
[Description("Número de parámetros de salida")]
|
|
||||||
[Category("Estadísticas")]
|
|
||||||
public int OutputParameterCount =>
|
|
||||||
Parameters?.Count(p => p.Direction == "OUT") ?? 0;
|
|
||||||
|
|
||||||
[DisplayName("Parámetros IN/OUT")]
|
|
||||||
[Description("Número de parámetros de entrada/salida")]
|
|
||||||
[Category("Estadísticas")]
|
|
||||||
public int InOutParameterCount =>
|
|
||||||
Parameters?.Count(p => p.Direction == "IN/OUT") ?? 0;
|
|
||||||
|
|
||||||
[DisplayName("Total Parámetros")]
|
|
||||||
[Description("Número total de parámetros")]
|
|
||||||
[Category("Estadísticas")]
|
|
||||||
public int TotalParameterCount =>
|
|
||||||
Parameters?.Count ?? 0;
|
|
||||||
|
|
||||||
public S7Function()
|
public S7Function()
|
||||||
{
|
{
|
||||||
ObjectType = S7ObjectType.Function;
|
|
||||||
Parameters = new List<FunctionParameter>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public S7Function(S7Block block)
|
||||||
/// Actualiza la representación formateada de la interfaz basada en los parámetros
|
|
||||||
/// </summary>
|
|
||||||
private void UpdateFormattedInterface()
|
|
||||||
{
|
{
|
||||||
if (Parameters == null || Parameters.Count == 0)
|
Block = block;
|
||||||
{
|
Name = block?.Name ?? string.Empty;
|
||||||
Interface = "// No hay parámetros definidos";
|
Description = block?.Title ?? string.Empty;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var builder = new System.Text.StringBuilder();
|
// Code would need to be extracted from the block
|
||||||
|
// This is just a placeholder
|
||||||
|
Code = "// Function code would be extracted here";
|
||||||
|
}
|
||||||
|
|
||||||
// Agrupar por dirección
|
public override string ToString()
|
||||||
var inputParams = Parameters.Where(p => p.Direction == "IN").ToList();
|
{
|
||||||
var outputParams = Parameters.Where(p => p.Direction == "OUT").ToList();
|
return Name;
|
||||||
var inOutParams = Parameters.Where(p => p.Direction == "IN/OUT").ToList();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Añadir tipo de retorno
|
public class S7FunctionParameter
|
||||||
builder.AppendLine($"FUNCTION {Name} : {ReturnType ?? "VOID"}");
|
{
|
||||||
builder.AppendLine();
|
public string Name { get; set; }
|
||||||
|
public string DataType { get; set; }
|
||||||
|
public string Comment { get; set; }
|
||||||
|
public string Direction { get; set; } // IN, OUT, IN_OUT
|
||||||
|
|
||||||
// Añadir parámetros de entrada
|
public override string ToString()
|
||||||
if (inputParams.Any())
|
{
|
||||||
{
|
return $"{Direction} {Name} : {DataType}";
|
||||||
builder.AppendLine("VAR_INPUT");
|
|
||||||
foreach (var param in inputParams)
|
|
||||||
{
|
|
||||||
builder.Append($" {param.Name} : {param.DataType}");
|
|
||||||
if (!string.IsNullOrEmpty(param.Description))
|
|
||||||
builder.Append($"; // {param.Description}");
|
|
||||||
builder.AppendLine();
|
|
||||||
}
|
|
||||||
builder.AppendLine("END_VAR");
|
|
||||||
builder.AppendLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Añadir parámetros de salida
|
|
||||||
if (outputParams.Any())
|
|
||||||
{
|
|
||||||
builder.AppendLine("VAR_OUTPUT");
|
|
||||||
foreach (var param in outputParams)
|
|
||||||
{
|
|
||||||
builder.Append($" {param.Name} : {param.DataType}");
|
|
||||||
if (!string.IsNullOrEmpty(param.Description))
|
|
||||||
builder.Append($"; // {param.Description}");
|
|
||||||
builder.AppendLine();
|
|
||||||
}
|
|
||||||
builder.AppendLine("END_VAR");
|
|
||||||
builder.AppendLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Añadir parámetros de entrada/salida
|
|
||||||
if (inOutParams.Any())
|
|
||||||
{
|
|
||||||
builder.AppendLine("VAR_IN_OUT");
|
|
||||||
foreach (var param in inOutParams)
|
|
||||||
{
|
|
||||||
builder.Append($" {param.Name} : {param.DataType}");
|
|
||||||
if (!string.IsNullOrEmpty(param.Description))
|
|
||||||
builder.Append($"; // {param.Description}");
|
|
||||||
builder.AppendLine();
|
|
||||||
}
|
|
||||||
builder.AppendLine("END_VAR");
|
|
||||||
}
|
|
||||||
|
|
||||||
Interface = builder.ToString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,120 +0,0 @@
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace S7Explorer.Models
|
|
||||||
{
|
|
||||||
public enum S7ObjectType
|
|
||||||
{
|
|
||||||
Project,
|
|
||||||
Device,
|
|
||||||
Folder,
|
|
||||||
DataBlock,
|
|
||||||
FunctionBlock,
|
|
||||||
Function,
|
|
||||||
Organization,
|
|
||||||
Symbol
|
|
||||||
}
|
|
||||||
|
|
||||||
public class S7Object : INotifyPropertyChanged
|
|
||||||
{
|
|
||||||
private string _name;
|
|
||||||
private string _description;
|
|
||||||
private bool _isExpanded;
|
|
||||||
|
|
||||||
public string Id { get; set; }
|
|
||||||
public string Number { get; set; }
|
|
||||||
|
|
||||||
[DisplayName("Nombre")]
|
|
||||||
public string Name
|
|
||||||
{
|
|
||||||
get => _name;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_name != value)
|
|
||||||
{
|
|
||||||
_name = value;
|
|
||||||
OnPropertyChanged(nameof(Name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[DisplayName("Descripción")]
|
|
||||||
public string Description
|
|
||||||
{
|
|
||||||
get => _description;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_description != value)
|
|
||||||
{
|
|
||||||
_description = value;
|
|
||||||
OnPropertyChanged(nameof(Description));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Browsable(false)]
|
|
||||||
public S7ObjectType ObjectType { get; set; }
|
|
||||||
|
|
||||||
[Browsable(false)]
|
|
||||||
public string IconSource => GetIconPath();
|
|
||||||
|
|
||||||
[Browsable(false)]
|
|
||||||
[JsonIgnore]
|
|
||||||
public S7Object Parent { get; set; }
|
|
||||||
|
|
||||||
[Browsable(false)]
|
|
||||||
public ObservableCollection<S7Object> Children { get; set; } = new ObservableCollection<S7Object>();
|
|
||||||
|
|
||||||
[Browsable(false)]
|
|
||||||
public bool IsExpanded
|
|
||||||
{
|
|
||||||
get => _isExpanded;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_isExpanded != value)
|
|
||||||
{
|
|
||||||
_isExpanded = value;
|
|
||||||
OnPropertyChanged(nameof(IsExpanded));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Para búsquedas de texto
|
|
||||||
[Browsable(false)]
|
|
||||||
public bool ContainsText(string searchText)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(searchText))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
searchText = searchText.ToLowerInvariant();
|
|
||||||
|
|
||||||
return Name?.ToLowerInvariant().Contains(searchText) == true ||
|
|
||||||
Description?.ToLowerInvariant().Contains(searchText) == true ||
|
|
||||||
Number?.ToLowerInvariant().Contains(searchText) == true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetIconPath()
|
|
||||||
{
|
|
||||||
return ObjectType switch
|
|
||||||
{
|
|
||||||
S7ObjectType.Project => "/Resources/project.png",
|
|
||||||
S7ObjectType.Device => "/Resources/device.png",
|
|
||||||
S7ObjectType.Folder => "/Resources/folder.png",
|
|
||||||
S7ObjectType.DataBlock => "/Resources/db.png",
|
|
||||||
S7ObjectType.FunctionBlock => "/Resources/fb.png",
|
|
||||||
S7ObjectType.Function => "/Resources/fc.png",
|
|
||||||
S7ObjectType.Organization => "/Resources/ob.png",
|
|
||||||
S7ObjectType.Symbol => "/Resources/symbol.png",
|
|
||||||
_ => "/Resources/default.png"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public event PropertyChangedEventHandler PropertyChanged;
|
|
||||||
|
|
||||||
protected virtual void OnPropertyChanged(string propertyName)
|
|
||||||
{
|
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,96 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace S7Explorer.Models
|
|
||||||
{
|
|
||||||
public class S7Project : S7Object
|
|
||||||
{
|
|
||||||
private string _filePath;
|
|
||||||
private string _version;
|
|
||||||
private DateTime _created;
|
|
||||||
private DateTime _lastModified;
|
|
||||||
|
|
||||||
[DisplayName("Ruta de Archivo")]
|
|
||||||
public string FilePath
|
|
||||||
{
|
|
||||||
get => _filePath;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_filePath != value)
|
|
||||||
{
|
|
||||||
_filePath = value;
|
|
||||||
OnPropertyChanged(nameof(FilePath));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[DisplayName("Versión STEP7")]
|
|
||||||
public string Version
|
|
||||||
{
|
|
||||||
get => _version;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_version != value)
|
|
||||||
{
|
|
||||||
_version = value;
|
|
||||||
OnPropertyChanged(nameof(Version));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[DisplayName("Creado")]
|
|
||||||
public DateTime Created
|
|
||||||
{
|
|
||||||
get => _created;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_created != value)
|
|
||||||
{
|
|
||||||
_created = value;
|
|
||||||
OnPropertyChanged(nameof(Created));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[DisplayName("Última Modificación")]
|
|
||||||
public DateTime LastModified
|
|
||||||
{
|
|
||||||
get => _lastModified;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_lastModified != value)
|
|
||||||
{
|
|
||||||
_lastModified = value;
|
|
||||||
OnPropertyChanged(nameof(LastModified));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Directorio base del proyecto (donde están las carpetas ombstx, YDBs, etc.)
|
|
||||||
[Browsable(false)]
|
|
||||||
public string ProjectDirectory => Path.GetDirectoryName(FilePath);
|
|
||||||
|
|
||||||
public S7Project()
|
|
||||||
{
|
|
||||||
ObjectType = S7ObjectType.Project;
|
|
||||||
Created = DateTime.Now;
|
|
||||||
LastModified = DateTime.Now;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inicializa un proyecto a partir de un archivo .s7p
|
|
||||||
public S7Project(string filePath) : this()
|
|
||||||
{
|
|
||||||
FilePath = filePath;
|
|
||||||
Name = Path.GetFileNameWithoutExtension(filePath);
|
|
||||||
|
|
||||||
// Trata de obtener fechas del archivo
|
|
||||||
if (File.Exists(filePath))
|
|
||||||
{
|
|
||||||
var fileInfo = new FileInfo(filePath);
|
|
||||||
Created = fileInfo.CreationTime;
|
|
||||||
LastModified = fileInfo.LastWriteTime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
using System.ComponentModel;
|
|
||||||
|
|
||||||
namespace S7Explorer.Models
|
|
||||||
{
|
|
||||||
public class S7Symbol : S7Object
|
|
||||||
{
|
|
||||||
private string _address;
|
|
||||||
private string _dataType;
|
|
||||||
private string _comment;
|
|
||||||
|
|
||||||
[DisplayName("Dirección")]
|
|
||||||
public string Address
|
|
||||||
{
|
|
||||||
get => _address;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_address != value)
|
|
||||||
{
|
|
||||||
_address = value;
|
|
||||||
OnPropertyChanged(nameof(Address));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[DisplayName("Tipo de Datos")]
|
|
||||||
public string DataType
|
|
||||||
{
|
|
||||||
get => _dataType;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_dataType != value)
|
|
||||||
{
|
|
||||||
_dataType = value;
|
|
||||||
OnPropertyChanged(nameof(DataType));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[DisplayName("Comentario")]
|
|
||||||
public string Comment
|
|
||||||
{
|
|
||||||
get => _comment;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_comment != value)
|
|
||||||
{
|
|
||||||
_comment = value;
|
|
||||||
OnPropertyChanged(nameof(Comment));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public S7Symbol()
|
|
||||||
{
|
|
||||||
ObjectType = S7ObjectType.Symbol;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sobrescribe el método de búsqueda de texto para incluir dirección y tipo de datos
|
|
||||||
public new bool ContainsText(string searchText)
|
|
||||||
{
|
|
||||||
if (base.ContainsText(searchText))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(searchText))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
searchText = searchText.ToLowerInvariant();
|
|
||||||
|
|
||||||
return Address?.ToLowerInvariant().Contains(searchText) == true ||
|
|
||||||
DataType?.ToLowerInvariant().Contains(searchText) == true ||
|
|
||||||
Comment?.ToLowerInvariant().Contains(searchText) == true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace S7Explorer.Models
|
||||||
|
{
|
||||||
|
public class SymbolItem : ProjectItem
|
||||||
|
{
|
||||||
|
public object? SymbolData { get; set; }
|
||||||
|
public string SymbolAddress { get; set; } = string.Empty;
|
||||||
|
public string SymbolDataType { get; set; } = string.Empty;
|
||||||
|
public string SymbolComment { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public override object? GetDetailsObject()
|
||||||
|
{
|
||||||
|
return SymbolData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string GetIcon()
|
||||||
|
{
|
||||||
|
return "Symbol";
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string GetExportText()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.AppendLine($"Symbol: {Name}");
|
||||||
|
sb.AppendLine($"Address: {SymbolAddress}");
|
||||||
|
sb.AppendLine($"Data Type: {SymbolDataType}");
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(SymbolComment))
|
||||||
|
sb.AppendLine($"Comment: {SymbolComment}");
|
||||||
|
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,139 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
using NDbfReader;
|
|
||||||
|
|
||||||
namespace S7Explorer.Parsers
|
|
||||||
{
|
|
||||||
public class DbfParser
|
|
||||||
{
|
|
||||||
// Lee campos específicos de un archivo DBF
|
|
||||||
public static List<Dictionary<string, string>> ReadDbfFile(string filePath, IEnumerable<string> fieldNames)
|
|
||||||
{
|
|
||||||
var result = new List<Dictionary<string, string>>();
|
|
||||||
|
|
||||||
if (!File.Exists(filePath))
|
|
||||||
throw new FileNotFoundException($"No se encontró el archivo DBF: {filePath}");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Abrir tabla DBF con codificación específica (importante para caracteres especiales en STEP7)
|
|
||||||
using var stream = File.OpenRead(filePath);
|
|
||||||
using var table = Table.Open(stream);
|
|
||||||
|
|
||||||
// Crear lector usando la API correcta
|
|
||||||
var reader = table.OpenReader();
|
|
||||||
|
|
||||||
while (reader.Read())
|
|
||||||
{
|
|
||||||
var record = new Dictionary<string, string>();
|
|
||||||
|
|
||||||
foreach (var fieldName in fieldNames)
|
|
||||||
{
|
|
||||||
// Obtener valor y convertir a string si no es null
|
|
||||||
var value = reader.GetValue(fieldName);
|
|
||||||
|
|
||||||
// Manejar los diferentes tipos de datos
|
|
||||||
if (value is byte[] byteValue)
|
|
||||||
{
|
|
||||||
// Para campos de tipo binario como MC5CODE
|
|
||||||
record[fieldName] = Encoding.GetEncoding(1252).GetString(byteValue);
|
|
||||||
}
|
|
||||||
else if (value is DateTime dateValue)
|
|
||||||
{
|
|
||||||
// Mantener formato consistente para fechas
|
|
||||||
record[fieldName] = dateValue.ToString("yyyy-MM-dd HH:mm:ss");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Para todos los demás tipos
|
|
||||||
record[fieldName] = value?.ToString() ?? string.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result.Add(record);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
throw new Exception($"Error al leer el archivo DBF {filePath}: {ex.Message}", ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convierte un string que representa un número a un entero opcional
|
|
||||||
public static int? StringToInt(string value)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(value))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
// Eliminar espacios y caracteres no numéricos iniciales
|
|
||||||
string trimmedValue = value.Trim();
|
|
||||||
int startIndex = 0;
|
|
||||||
while (startIndex < trimmedValue.Length && !char.IsDigit(trimmedValue[startIndex]))
|
|
||||||
startIndex++;
|
|
||||||
|
|
||||||
if (startIndex >= trimmedValue.Length)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
// Extraer solo los dígitos
|
|
||||||
string numericPart = new string(trimmedValue.Substring(startIndex)
|
|
||||||
.TakeWhile(char.IsDigit)
|
|
||||||
.ToArray());
|
|
||||||
|
|
||||||
if (int.TryParse(numericPart, out int result))
|
|
||||||
return result;
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convierte códigos Windows-1252 a UTF-8 para manejar caracteres especiales en STEP7
|
|
||||||
public static string ConvertCP1252ToUtf8(string input)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(input))
|
|
||||||
return string.Empty;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Primero decodificar como Windows-1252 y luego encodear como UTF-8
|
|
||||||
byte[] bytes = Encoding.GetEncoding(1252).GetBytes(input);
|
|
||||||
return Encoding.UTF8.GetString(bytes);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// En caso de error, devolver el string original
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Busca un archivo DBF en varias ubicaciones posibles basadas en el patrón de archivos STEP7
|
|
||||||
public static string FindDbfFile(string basePath, string relativePath)
|
|
||||||
{
|
|
||||||
string path = Path.Combine(basePath, relativePath);
|
|
||||||
if (File.Exists(path))
|
|
||||||
return path;
|
|
||||||
|
|
||||||
// Comprobar si se puede encontrar en un directorio padre
|
|
||||||
string parentPath = Directory.GetParent(basePath)?.FullName;
|
|
||||||
if (!string.IsNullOrEmpty(parentPath))
|
|
||||||
{
|
|
||||||
path = Path.Combine(parentPath, relativePath);
|
|
||||||
if (File.Exists(path))
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Intentar buscar por nombre de archivo en subdirectorios
|
|
||||||
string fileName = Path.GetFileName(relativePath);
|
|
||||||
foreach (var subdir in Directory.GetDirectories(basePath, "*", SearchOption.AllDirectories))
|
|
||||||
{
|
|
||||||
path = Path.Combine(subdir, fileName);
|
|
||||||
if (File.Exists(path))
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,439 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using S7Explorer.Models;
|
|
||||||
|
|
||||||
namespace S7Explorer.Parsers
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Parser específico para los bloques de función (FC) de STEP7
|
|
||||||
/// </summary>
|
|
||||||
public class FCParser
|
|
||||||
{
|
|
||||||
private readonly string _projectDirectory;
|
|
||||||
|
|
||||||
public FCParser(string projectDirectory)
|
|
||||||
{
|
|
||||||
_projectDirectory = projectDirectory;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Parsea los bloques FC desde los archivos SUBBLK.DBF del proyecto
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="subblockListId">ID de la lista de subbloques del dispositivo</param>
|
|
||||||
/// <returns>Lista de objetos S7Function representando los bloques FC</returns>
|
|
||||||
public List<S7Function> ParseFunctionBlocks(int subblockListId)
|
|
||||||
{
|
|
||||||
var functions = new List<S7Function>();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Construir la ruta al archivo SUBBLK.DBF que contiene información de los bloques
|
|
||||||
string subblockFolder = $"{_projectDirectory}\\ombstx\\offline\\{subblockListId:X8}";
|
|
||||||
string subblkPath = Path.Combine(subblockFolder, "SUBBLK.DBF");
|
|
||||||
|
|
||||||
if (!File.Exists(subblkPath))
|
|
||||||
{
|
|
||||||
return CreateSampleFunctions(); // En caso de que no exista, usar datos de muestra
|
|
||||||
}
|
|
||||||
|
|
||||||
// Leer datos del archivo DBF
|
|
||||||
var records = DbfParser.ReadDbfFile(subblkPath, new[]
|
|
||||||
{
|
|
||||||
"SUBBLKTYP", "BLKNUMBER", "BLKNAME", "AUTHOR", "FAMILY",
|
|
||||||
"VERSION", "CREATEDATE", "MODDATE", "INTERFLEN", "MC5LEN", "MC5CODE"
|
|
||||||
});
|
|
||||||
|
|
||||||
// Filtrar solo los registros que correspondan a FCs (tipo 00003)
|
|
||||||
var fcRecords = records.Where(r => r["SUBBLKTYP"] == "00003").ToList();
|
|
||||||
|
|
||||||
foreach (var record in fcRecords)
|
|
||||||
{
|
|
||||||
// Convertir número de bloque a entero
|
|
||||||
if (!int.TryParse(record["BLKNUMBER"], out int blockNumber))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Crear objeto FC con los datos del registro
|
|
||||||
var fc = new S7Function
|
|
||||||
{
|
|
||||||
Number = $"FC{blockNumber}",
|
|
||||||
Name = DbfParser.ConvertCP1252ToUtf8(record["BLKNAME"]).Trim(),
|
|
||||||
AuthorName = DbfParser.ConvertCP1252ToUtf8(record["AUTHOR"]).Trim(),
|
|
||||||
Family = DbfParser.ConvertCP1252ToUtf8(record["FAMILY"]).Trim(),
|
|
||||||
Version = record["VERSION"].Trim(),
|
|
||||||
Size = ParseBlockSize(record["MC5LEN"]),
|
|
||||||
ObjectType = S7ObjectType.Function,
|
|
||||||
Language = DetermineLanguageFromMC5Code(record["MC5CODE"]),
|
|
||||||
ReturnType = DetermineReturnTypeFromMC5Code(record["MC5CODE"])
|
|
||||||
};
|
|
||||||
|
|
||||||
// Intentar extraer fecha de modificación
|
|
||||||
if (DateTime.TryParse(record["MODDATE"], out DateTime modDate))
|
|
||||||
{
|
|
||||||
fc.Modified = modDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extraer más información del código MC5
|
|
||||||
ExtractAdditionalInfoFromMC5Code(fc, record["MC5CODE"]);
|
|
||||||
|
|
||||||
// Analizar variables de entrada/salida del interfaz
|
|
||||||
AnalyzeInterfaceData(fc, record["MC5CODE"]);
|
|
||||||
|
|
||||||
functions.Add(fc);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ordenar los bloques por número
|
|
||||||
functions = functions.OrderBy(f => ExtractNumber(f.Number)).ToList();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// En caso de error, añadir un FC de error para informar al usuario
|
|
||||||
var errorFc = new S7Function
|
|
||||||
{
|
|
||||||
Name = "Error_Parsing_FC",
|
|
||||||
Number = "FC999",
|
|
||||||
Description = $"Error al parsear FCs: {ex.Message}",
|
|
||||||
ObjectType = S7ObjectType.Function
|
|
||||||
};
|
|
||||||
functions.Add(errorFc);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Si no se encontraron FCs, usar datos de muestra
|
|
||||||
if (functions.Count == 0)
|
|
||||||
{
|
|
||||||
return CreateSampleFunctions();
|
|
||||||
}
|
|
||||||
|
|
||||||
return functions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Extrae el número de un bloque a partir de su identificador (ej: "FC5" -> 5)
|
|
||||||
/// </summary>
|
|
||||||
private int ExtractNumber(string blockId)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(blockId) || blockId.Length < 3)
|
|
||||||
return 9999; // Valor alto para ordenar al final
|
|
||||||
|
|
||||||
if (int.TryParse(blockId.Substring(2), out int number))
|
|
||||||
return number;
|
|
||||||
|
|
||||||
return 9999;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Parsea el tamaño del bloque a partir del valor MC5LEN
|
|
||||||
/// </summary>
|
|
||||||
private int ParseBlockSize(string mc5Len)
|
|
||||||
{
|
|
||||||
if (int.TryParse(mc5Len, out int size))
|
|
||||||
return size;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determina el lenguaje de programación a partir del código MC5
|
|
||||||
/// </summary>
|
|
||||||
private string DetermineLanguageFromMC5Code(string mc5Code)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(mc5Code))
|
|
||||||
return "Desconocido";
|
|
||||||
|
|
||||||
// Esta es una lógica simplificada. En una implementación real,
|
|
||||||
// necesitarías analizar patrones específicos en el código MC5
|
|
||||||
if (mc5Code.Contains("STL") || mc5Code.Contains("AWL"))
|
|
||||||
return "AWL";
|
|
||||||
else if (mc5Code.Contains("SCL"))
|
|
||||||
return "SCL";
|
|
||||||
else if (mc5Code.Contains("GRAPH"))
|
|
||||||
return "GRAPH";
|
|
||||||
else if (mc5Code.Contains("KOP") || mc5Code.Contains("LAD"))
|
|
||||||
return "KOP";
|
|
||||||
else if (mc5Code.Contains("FUP") || mc5Code.Contains("FBD"))
|
|
||||||
return "FUP";
|
|
||||||
|
|
||||||
// Por defecto asumimos AWL (lenguaje más común)
|
|
||||||
return "AWL";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determina el tipo de retorno a partir del código MC5
|
|
||||||
/// </summary>
|
|
||||||
private string DetermineReturnTypeFromMC5Code(string mc5Code)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(mc5Code))
|
|
||||||
return "VOID";
|
|
||||||
|
|
||||||
// Esta es una lógica simplificada. En una implementación real,
|
|
||||||
// necesitarías analizar patrones específicos en el código MC5
|
|
||||||
if (mc5Code.Contains("BOOL") && mc5Code.Contains("RET_VAL"))
|
|
||||||
return "BOOL";
|
|
||||||
else if (mc5Code.Contains("INT") && mc5Code.Contains("RET_VAL"))
|
|
||||||
return "INT";
|
|
||||||
else if (mc5Code.Contains("REAL") && mc5Code.Contains("RET_VAL"))
|
|
||||||
return "REAL";
|
|
||||||
else if (mc5Code.Contains("WORD") && mc5Code.Contains("RET_VAL"))
|
|
||||||
return "WORD";
|
|
||||||
else if (mc5Code.Contains("DWORD") && mc5Code.Contains("RET_VAL"))
|
|
||||||
return "DWORD";
|
|
||||||
else if (mc5Code.Contains("TIME") && mc5Code.Contains("RET_VAL"))
|
|
||||||
return "TIME";
|
|
||||||
|
|
||||||
// Buscar cualquier tipo de dato asociado a RET_VAL
|
|
||||||
int retValPos = mc5Code.IndexOf("RET_VAL");
|
|
||||||
if (retValPos > 0)
|
|
||||||
{
|
|
||||||
string[] dataTypes = { "BYTE", "DINT", "CHAR", "STRING", "DATE", "TIME_OF_DAY", "DATE_AND_TIME" };
|
|
||||||
foreach (string type in dataTypes)
|
|
||||||
{
|
|
||||||
if (mc5Code.IndexOf(type, retValPos - 50, 100) > 0)
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "VOID";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Extrae información adicional del código MC5
|
|
||||||
/// </summary>
|
|
||||||
private void ExtractAdditionalInfoFromMC5Code(S7Function fc, string mc5Code)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(mc5Code))
|
|
||||||
return;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Extraer descripción (comentario del bloque)
|
|
||||||
int descStart = mc5Code.IndexOf("//");
|
|
||||||
if (descStart >= 0)
|
|
||||||
{
|
|
||||||
int descEnd = mc5Code.IndexOf('\n', descStart);
|
|
||||||
if (descEnd > descStart)
|
|
||||||
{
|
|
||||||
string comment = mc5Code.Substring(descStart + 2, descEnd - descStart - 2).Trim();
|
|
||||||
if (!string.IsNullOrEmpty(comment))
|
|
||||||
fc.Description = DbfParser.ConvertCP1252ToUtf8(comment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Añadir otros metadatos que se puedan extraer
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// Ignorar errores en la extracción para no detener el proceso
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Analiza los datos de interfaz para extraer parámetros de entrada/salida
|
|
||||||
/// </summary>
|
|
||||||
private void AnalyzeInterfaceData(S7Function fc, string mc5Code)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(mc5Code))
|
|
||||||
return;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Crear lista de parámetros
|
|
||||||
fc.Parameters = new List<FunctionParameter>();
|
|
||||||
|
|
||||||
// Buscar sección de interfaz
|
|
||||||
int varInputPos = mc5Code.IndexOf("VAR_INPUT");
|
|
||||||
int varOutputPos = mc5Code.IndexOf("VAR_OUTPUT");
|
|
||||||
int varInOutPos = mc5Code.IndexOf("VAR_IN_OUT");
|
|
||||||
|
|
||||||
// Procesar parámetros de entrada
|
|
||||||
if (varInputPos >= 0)
|
|
||||||
{
|
|
||||||
int endPos = DetermineNextSectionPos(mc5Code, varInputPos);
|
|
||||||
string inputSection = mc5Code.Substring(varInputPos, endPos - varInputPos);
|
|
||||||
ExtractParameters(inputSection, "IN", fc.Parameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Procesar parámetros de salida
|
|
||||||
if (varOutputPos >= 0)
|
|
||||||
{
|
|
||||||
int endPos = DetermineNextSectionPos(mc5Code, varOutputPos);
|
|
||||||
string outputSection = mc5Code.Substring(varOutputPos, endPos - varOutputPos);
|
|
||||||
ExtractParameters(outputSection, "OUT", fc.Parameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Procesar parámetros de entrada/salida
|
|
||||||
if (varInOutPos >= 0)
|
|
||||||
{
|
|
||||||
int endPos = DetermineNextSectionPos(mc5Code, varInOutPos);
|
|
||||||
string inOutSection = mc5Code.Substring(varInOutPos, endPos - varInOutPos);
|
|
||||||
ExtractParameters(inOutSection, "IN/OUT", fc.Parameters);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// Ignorar errores en la extracción para no detener el proceso
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determina la posición de la siguiente sección en el código
|
|
||||||
/// </summary>
|
|
||||||
private int DetermineNextSectionPos(string code, int startPos)
|
|
||||||
{
|
|
||||||
string[] sections = { "VAR_INPUT", "VAR_OUTPUT", "VAR_IN_OUT", "VAR_TEMP", "VAR", "BEGIN" };
|
|
||||||
|
|
||||||
int minPos = code.Length;
|
|
||||||
foreach (string section in sections)
|
|
||||||
{
|
|
||||||
int pos = code.IndexOf(section, startPos + 1);
|
|
||||||
if (pos > 0 && pos < minPos)
|
|
||||||
minPos = pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
return minPos;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Extrae parámetros individuales de una sección de interfaz
|
|
||||||
/// </summary>
|
|
||||||
private void ExtractParameters(string section, string direction, List<FunctionParameter> parameters)
|
|
||||||
{
|
|
||||||
// Dividir por líneas
|
|
||||||
string[] lines = section.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
|
|
||||||
foreach (string line in lines)
|
|
||||||
{
|
|
||||||
string trimmedLine = line.Trim();
|
|
||||||
|
|
||||||
// Ignorar líneas de declaración de sección, comentarios o END_VAR
|
|
||||||
if (trimmedLine.StartsWith("VAR_") || trimmedLine.StartsWith("//") ||
|
|
||||||
trimmedLine.StartsWith("END_VAR") || string.IsNullOrWhiteSpace(trimmedLine))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Procesar línea con declaración de parámetro
|
|
||||||
int colonPos = trimmedLine.IndexOf(':');
|
|
||||||
if (colonPos > 0)
|
|
||||||
{
|
|
||||||
string paramName = trimmedLine.Substring(0, colonPos).Trim();
|
|
||||||
|
|
||||||
// Extraer tipo y comentario
|
|
||||||
string remainder = trimmedLine.Substring(colonPos + 1).Trim();
|
|
||||||
string paramType = remainder;
|
|
||||||
string comment = string.Empty;
|
|
||||||
|
|
||||||
// Verificar si hay comentario en la línea
|
|
||||||
int commentPos = remainder.IndexOf("//");
|
|
||||||
if (commentPos > 0)
|
|
||||||
{
|
|
||||||
paramType = remainder.Substring(0, commentPos).Trim();
|
|
||||||
comment = remainder.Substring(commentPos + 2).Trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Limpiar cualquier punto y coma al final del tipo
|
|
||||||
if (paramType.EndsWith(";"))
|
|
||||||
paramType = paramType.Substring(0, paramType.Length - 1).Trim();
|
|
||||||
|
|
||||||
// Añadir parámetro a la lista
|
|
||||||
parameters.Add(new FunctionParameter
|
|
||||||
{
|
|
||||||
Name = paramName,
|
|
||||||
DataType = paramType,
|
|
||||||
Direction = direction,
|
|
||||||
Description = comment
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Crea bloques FC de ejemplo cuando no se pueden parsear los reales
|
|
||||||
/// </summary>
|
|
||||||
private List<S7Function> CreateSampleFunctions()
|
|
||||||
{
|
|
||||||
return new List<S7Function>
|
|
||||||
{
|
|
||||||
new S7Function {
|
|
||||||
Name = "Calc_Setpoint",
|
|
||||||
Number = "FC1",
|
|
||||||
Size = 124,
|
|
||||||
ReturnType = "REAL",
|
|
||||||
Description = "Cálculo de punto de consigna",
|
|
||||||
Modified = DateTime.Now.AddDays(-8),
|
|
||||||
Language = "SCL",
|
|
||||||
Parameters = new List<FunctionParameter>
|
|
||||||
{
|
|
||||||
new FunctionParameter { Name = "Target", DataType = "REAL", Direction = "IN", Description = "Valor objetivo" },
|
|
||||||
new FunctionParameter { Name = "Actual", DataType = "REAL", Direction = "IN", Description = "Valor actual" },
|
|
||||||
new FunctionParameter { Name = "Gain", DataType = "REAL", Direction = "IN", Description = "Ganancia" }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new S7Function {
|
|
||||||
Name = "Scale_Analog",
|
|
||||||
Number = "FC2",
|
|
||||||
Size = 68,
|
|
||||||
ReturnType = "REAL",
|
|
||||||
Description = "Escalado de valor analógico",
|
|
||||||
Modified = DateTime.Now.AddDays(-12),
|
|
||||||
Language = "AWL",
|
|
||||||
Parameters = new List<FunctionParameter>
|
|
||||||
{
|
|
||||||
new FunctionParameter { Name = "Raw", DataType = "INT", Direction = "IN", Description = "Valor bruto" },
|
|
||||||
new FunctionParameter { Name = "RawLow", DataType = "INT", Direction = "IN", Description = "Valor mínimo bruto" },
|
|
||||||
new FunctionParameter { Name = "RawHigh", DataType = "INT", Direction = "IN", Description = "Valor máximo bruto" },
|
|
||||||
new FunctionParameter { Name = "ScaleLow", DataType = "REAL", Direction = "IN", Description = "Valor mínimo escalado" },
|
|
||||||
new FunctionParameter { Name = "ScaleHigh", DataType = "REAL", Direction = "IN", Description = "Valor máximo escalado" }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new S7Function {
|
|
||||||
Name = "CheckAlarms",
|
|
||||||
Number = "FC3",
|
|
||||||
Size = 154,
|
|
||||||
ReturnType = "BOOL",
|
|
||||||
Description = "Verificación de alarmas",
|
|
||||||
Modified = DateTime.Now.AddDays(-5),
|
|
||||||
Language = "KOP",
|
|
||||||
Parameters = new List<FunctionParameter>
|
|
||||||
{
|
|
||||||
new FunctionParameter { Name = "Value", DataType = "REAL", Direction = "IN", Description = "Valor a comprobar" },
|
|
||||||
new FunctionParameter { Name = "LowLimit", DataType = "REAL", Direction = "IN", Description = "Límite inferior" },
|
|
||||||
new FunctionParameter { Name = "HighLimit", DataType = "REAL", Direction = "IN", Description = "Límite superior" },
|
|
||||||
new FunctionParameter { Name = "AlarmStatus", DataType = "WORD", Direction = "OUT", Description = "Estado de alarmas" }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new S7Function {
|
|
||||||
Name = "Timer_Control",
|
|
||||||
Number = "FC4",
|
|
||||||
Size = 86,
|
|
||||||
ReturnType = "VOID",
|
|
||||||
Description = "Control de temporizadores",
|
|
||||||
Modified = DateTime.Now.AddDays(-3),
|
|
||||||
Language = "FUP",
|
|
||||||
Parameters = new List<FunctionParameter>
|
|
||||||
{
|
|
||||||
new FunctionParameter { Name = "Start", DataType = "BOOL", Direction = "IN", Description = "Iniciar temporizador" },
|
|
||||||
new FunctionParameter { Name = "Duration", DataType = "TIME", Direction = "IN", Description = "Duración" },
|
|
||||||
new FunctionParameter { Name = "TimerNo", DataType = "INT", Direction = "IN", Description = "Número de temporizador" },
|
|
||||||
new FunctionParameter { Name = "Status", DataType = "BOOL", Direction = "OUT", Description = "Estado (activo/inactivo)" },
|
|
||||||
new FunctionParameter { Name = "ElapsedTime", DataType = "TIME", Direction = "OUT", Description = "Tiempo transcurrido" }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new S7Function {
|
|
||||||
Name = "String_Process",
|
|
||||||
Number = "FC5",
|
|
||||||
Size = 210,
|
|
||||||
ReturnType = "INT",
|
|
||||||
Description = "Procesamiento de cadenas",
|
|
||||||
Modified = DateTime.Now.AddDays(-1),
|
|
||||||
Language = "SCL",
|
|
||||||
Parameters = new List<FunctionParameter>
|
|
||||||
{
|
|
||||||
new FunctionParameter { Name = "InStr", DataType = "STRING", Direction = "IN", Description = "Cadena de entrada" },
|
|
||||||
new FunctionParameter { Name = "Operation", DataType = "INT", Direction = "IN", Description = "Operación a realizar" },
|
|
||||||
new FunctionParameter { Name = "OutStr", DataType = "STRING", Direction = "OUT", Description = "Cadena de salida" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,412 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using S7Explorer.Models;
|
|
||||||
|
|
||||||
namespace S7Explorer.Parsers
|
|
||||||
{
|
|
||||||
public class S7ProjectParser
|
|
||||||
{
|
|
||||||
private readonly string _projectFilePath;
|
|
||||||
private readonly string _projectDirectory;
|
|
||||||
|
|
||||||
public S7ProjectParser(string projectFilePath)
|
|
||||||
{
|
|
||||||
_projectFilePath = projectFilePath;
|
|
||||||
_projectDirectory = Path.GetDirectoryName(projectFilePath);
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(_projectDirectory))
|
|
||||||
throw new ArgumentException("No se pudo determinar el directorio del proyecto");
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<S7Project> ParseProjectAsync()
|
|
||||||
{
|
|
||||||
return await Task.Run(() =>
|
|
||||||
{
|
|
||||||
// Crear objeto de proyecto
|
|
||||||
var project = new S7Project(_projectFilePath);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Estructura básica del proyecto
|
|
||||||
var devicesFolder = new S7Object
|
|
||||||
{
|
|
||||||
Name = "Dispositivos",
|
|
||||||
ObjectType = S7ObjectType.Folder,
|
|
||||||
Parent = project
|
|
||||||
};
|
|
||||||
project.Children.Add(devicesFolder);
|
|
||||||
|
|
||||||
// Parsear dispositivos
|
|
||||||
ParseDevices(devicesFolder);
|
|
||||||
|
|
||||||
// Para uso en el futuro: más carpetas de alto nivel
|
|
||||||
var sharedFolder = new S7Object
|
|
||||||
{
|
|
||||||
Name = "Datos Globales",
|
|
||||||
ObjectType = S7ObjectType.Folder,
|
|
||||||
Parent = project
|
|
||||||
};
|
|
||||||
project.Children.Add(sharedFolder);
|
|
||||||
|
|
||||||
return project;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// En caso de error, al menos retornamos un proyecto básico con el error
|
|
||||||
var errorObject = new S7Object
|
|
||||||
{
|
|
||||||
Name = "Error al parsear proyecto",
|
|
||||||
Description = ex.Message,
|
|
||||||
ObjectType = S7ObjectType.Folder,
|
|
||||||
Parent = project
|
|
||||||
};
|
|
||||||
project.Children.Add(errorObject);
|
|
||||||
|
|
||||||
return project;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ParseDevices(S7Object devicesFolder)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Obtener lista de dispositivos a partir del archivo de información
|
|
||||||
var deviceIdInfos = ParseDeviceIdInfos();
|
|
||||||
|
|
||||||
foreach (var deviceInfo in deviceIdInfos)
|
|
||||||
{
|
|
||||||
var device = new S7Object
|
|
||||||
{
|
|
||||||
Name = deviceInfo.Name,
|
|
||||||
ObjectType = S7ObjectType.Device,
|
|
||||||
Parent = devicesFolder
|
|
||||||
};
|
|
||||||
devicesFolder.Children.Add(device);
|
|
||||||
|
|
||||||
// Crear carpetas para cada tipo de bloque
|
|
||||||
var dbFolder = CreateBlockFolder(device, "Bloques de datos (DB)");
|
|
||||||
var fbFolder = CreateBlockFolder(device, "Bloques de función (FB)");
|
|
||||||
var fcFolder = CreateBlockFolder(device, "Funciones (FC)");
|
|
||||||
var obFolder = CreateBlockFolder(device, "Bloques de organización (OB)");
|
|
||||||
var symbolsFolder = CreateBlockFolder(device, "Tabla de símbolos");
|
|
||||||
|
|
||||||
// Parsear bloques y símbolos si se dispone de IDs
|
|
||||||
if (deviceInfo.SymbolListId.HasValue)
|
|
||||||
{
|
|
||||||
ParseSymbols(symbolsFolder, deviceInfo.SymbolListId.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (deviceInfo.SubblockListId.HasValue)
|
|
||||||
{
|
|
||||||
ParseBlocks(dbFolder, fbFolder, fcFolder, obFolder, deviceInfo.SubblockListId.Value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// Si falla el parseo, añadimos un nodo de error pero seguimos con el resto
|
|
||||||
var errorNode = new S7Object
|
|
||||||
{
|
|
||||||
Name = "Error al parsear dispositivos",
|
|
||||||
Description = ex.Message,
|
|
||||||
ObjectType = S7ObjectType.Folder,
|
|
||||||
Parent = devicesFolder
|
|
||||||
};
|
|
||||||
devicesFolder.Children.Add(errorNode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private S7Object CreateBlockFolder(S7Object parent, string name)
|
|
||||||
{
|
|
||||||
var folder = new S7Object
|
|
||||||
{
|
|
||||||
Name = name,
|
|
||||||
ObjectType = S7ObjectType.Folder,
|
|
||||||
Parent = parent
|
|
||||||
};
|
|
||||||
parent.Children.Add(folder);
|
|
||||||
return folder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<DeviceIdInfo> ParseDeviceIdInfos()
|
|
||||||
{
|
|
||||||
var result = new List<DeviceIdInfo>();
|
|
||||||
|
|
||||||
// Esto es una simplificación - en una implementación real
|
|
||||||
// necesitarías parsear los archivos reales como en el proyecto C++
|
|
||||||
var s7resoffPath = Path.Combine(_projectDirectory, "hrs", "S7RESOFF.DBF");
|
|
||||||
|
|
||||||
if (File.Exists(s7resoffPath))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Leer la tabla de dispositivos
|
|
||||||
var records = DbfParser.ReadDbfFile(s7resoffPath, new[] { "ID", "NAME", "RSRVD4_L" });
|
|
||||||
|
|
||||||
foreach (var record in records)
|
|
||||||
{
|
|
||||||
var device = new DeviceIdInfo
|
|
||||||
{
|
|
||||||
Name = DbfParser.ConvertCP1252ToUtf8(record["NAME"]),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Procesamiento simplificado para IDs de Subblock y Symbol List
|
|
||||||
device.SubblockListId = DbfParser.StringToInt(record["RSRVD4_L"]);
|
|
||||||
|
|
||||||
// NOTA: En una implementación completa, tendrías que leer linkhrs.lnk
|
|
||||||
// para obtener SubblockListId y SymbolListId reales
|
|
||||||
|
|
||||||
result.Add(device);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
// Error de parseo - ignorar y continuar
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Si no se encuentran dispositivos, crear uno de ejemplo
|
|
||||||
if (result.Count == 0)
|
|
||||||
{
|
|
||||||
result.Add(new DeviceIdInfo
|
|
||||||
{
|
|
||||||
Name = "Dispositivo de ejemplo"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ParseSymbols(S7Object symbolsFolder, int symbolListId)
|
|
||||||
{
|
|
||||||
// Esta es una implementación de muestra
|
|
||||||
// En una versión completa, leerías los archivos YDBs reales
|
|
||||||
|
|
||||||
// Crear algunos símbolos de ejemplo
|
|
||||||
var symbols = new List<S7Symbol>
|
|
||||||
{
|
|
||||||
new S7Symbol {
|
|
||||||
Name = "Motor_Start",
|
|
||||||
Address = "I0.0",
|
|
||||||
DataType = "BOOL",
|
|
||||||
Comment = "Pulsador de inicio del motor"
|
|
||||||
},
|
|
||||||
new S7Symbol {
|
|
||||||
Name = "Motor_Stop",
|
|
||||||
Address = "I0.1",
|
|
||||||
DataType = "BOOL",
|
|
||||||
Comment = "Pulsador de parada del motor"
|
|
||||||
},
|
|
||||||
new S7Symbol {
|
|
||||||
Name = "Motor_Running",
|
|
||||||
Address = "Q0.0",
|
|
||||||
DataType = "BOOL",
|
|
||||||
Comment = "Motor en marcha"
|
|
||||||
},
|
|
||||||
new S7Symbol {
|
|
||||||
Name = "Temperature",
|
|
||||||
Address = "IW64",
|
|
||||||
DataType = "INT",
|
|
||||||
Comment = "Temperatura del proceso"
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var symbol in symbols)
|
|
||||||
{
|
|
||||||
symbol.Parent = symbolsFolder;
|
|
||||||
symbolsFolder.Children.Add(symbol);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ParseBlocks(S7Object dbFolder, S7Object fbFolder, S7Object fcFolder, S7Object obFolder, int subblockListId)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Usar parser específico para las funciones (FC)
|
|
||||||
var fcParser = new FCParser(_projectDirectory);
|
|
||||||
var functions = fcParser.ParseFunctionBlocks(subblockListId);
|
|
||||||
|
|
||||||
// Añadir bloques FC al árbol de proyecto
|
|
||||||
foreach (var fc in functions)
|
|
||||||
{
|
|
||||||
fc.Parent = fcFolder;
|
|
||||||
fcFolder.Children.Add(fc);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Para los otros tipos de bloques, seguimos usando los ejemplos por ahora
|
|
||||||
// En una implementación completa, crearíamos parsers específicos para cada tipo
|
|
||||||
AddSampleDataBlocks(dbFolder);
|
|
||||||
AddSampleFunctionBlocks(fbFolder);
|
|
||||||
AddSampleOrgBlocks(obFolder);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// En caso de error, añadimos un objeto de error informativo
|
|
||||||
var errorObject = new S7Object
|
|
||||||
{
|
|
||||||
Name = "Error en parseo de bloques",
|
|
||||||
Description = ex.Message,
|
|
||||||
ObjectType = S7ObjectType.Folder
|
|
||||||
};
|
|
||||||
|
|
||||||
// Lo añadimos a cada carpeta para que sea visible
|
|
||||||
dbFolder.Children.Add(errorObject);
|
|
||||||
fbFolder.Children.Add(new S7Object
|
|
||||||
{
|
|
||||||
Name = errorObject.Name,
|
|
||||||
Description = errorObject.Description,
|
|
||||||
ObjectType = errorObject.ObjectType
|
|
||||||
});
|
|
||||||
fcFolder.Children.Add(new S7Object
|
|
||||||
{
|
|
||||||
Name = errorObject.Name,
|
|
||||||
Description = errorObject.Description,
|
|
||||||
ObjectType = errorObject.ObjectType
|
|
||||||
});
|
|
||||||
obFolder.Children.Add(new S7Object
|
|
||||||
{
|
|
||||||
Name = errorObject.Name,
|
|
||||||
Description = errorObject.Description,
|
|
||||||
ObjectType = errorObject.ObjectType
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddSampleDataBlocks(S7Object dbFolder)
|
|
||||||
{
|
|
||||||
var dbs = new List<S7DataBlock>
|
|
||||||
{
|
|
||||||
new S7DataBlock {
|
|
||||||
Name = "Datos_proceso",
|
|
||||||
Number = "DB1",
|
|
||||||
Size = 124,
|
|
||||||
IsInstanceDb = false,
|
|
||||||
Description = "Datos de proceso",
|
|
||||||
Modified = DateTime.Now.AddDays(-5)
|
|
||||||
},
|
|
||||||
new S7DataBlock {
|
|
||||||
Name = "Parámetros",
|
|
||||||
Number = "DB2",
|
|
||||||
Size = 234,
|
|
||||||
IsInstanceDb = false,
|
|
||||||
Description = "Parámetros de configuración",
|
|
||||||
Modified = DateTime.Now.AddDays(-10)
|
|
||||||
},
|
|
||||||
new S7DataBlock {
|
|
||||||
Name = "Motor_Inst",
|
|
||||||
Number = "DB10",
|
|
||||||
Size = 86,
|
|
||||||
IsInstanceDb = true,
|
|
||||||
Description = "Instancia de FB1",
|
|
||||||
Modified = DateTime.Now.AddDays(-2)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var db in dbs)
|
|
||||||
{
|
|
||||||
db.Parent = dbFolder;
|
|
||||||
dbFolder.Children.Add(db);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddSampleFunctionBlocks(S7Object fbFolder)
|
|
||||||
{
|
|
||||||
var fbs = new List<S7FunctionBlock>
|
|
||||||
{
|
|
||||||
new S7FunctionBlock {
|
|
||||||
Name = "Motor_Control",
|
|
||||||
Number = "FB1",
|
|
||||||
Size = 328,
|
|
||||||
Language = "SCL",
|
|
||||||
Description = "Control de motor",
|
|
||||||
Modified = DateTime.Now.AddDays(-15)
|
|
||||||
},
|
|
||||||
new S7FunctionBlock {
|
|
||||||
Name = "PID_Control",
|
|
||||||
Number = "FB2",
|
|
||||||
Size = 512,
|
|
||||||
Language = "SCL",
|
|
||||||
Description = "Controlador PID",
|
|
||||||
Modified = DateTime.Now.AddDays(-20)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var fb in fbs)
|
|
||||||
{
|
|
||||||
fb.Parent = fbFolder;
|
|
||||||
fbFolder.Children.Add(fb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddSampleFunctions(S7Object fcFolder)
|
|
||||||
{
|
|
||||||
var fcs = new List<S7Function>
|
|
||||||
{
|
|
||||||
new S7Function {
|
|
||||||
Name = "Calc_Setpoint",
|
|
||||||
Number = "FC1",
|
|
||||||
Size = 124,
|
|
||||||
ReturnType = "REAL",
|
|
||||||
Description = "Cálculo de punto de consigna",
|
|
||||||
Modified = DateTime.Now.AddDays(-8)
|
|
||||||
},
|
|
||||||
new S7Function {
|
|
||||||
Name = "Scale_Analog",
|
|
||||||
Number = "FC2",
|
|
||||||
Size = 68,
|
|
||||||
ReturnType = "REAL",
|
|
||||||
Description = "Escalado de valor analógico",
|
|
||||||
Modified = DateTime.Now.AddDays(-12)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var fc in fcs)
|
|
||||||
{
|
|
||||||
fc.Parent = fcFolder;
|
|
||||||
fcFolder.Children.Add(fc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddSampleOrgBlocks(S7Object obFolder)
|
|
||||||
{
|
|
||||||
var obs = new List<S7Block>
|
|
||||||
{
|
|
||||||
new S7Block {
|
|
||||||
Name = "Main",
|
|
||||||
Number = "OB1",
|
|
||||||
Size = 256,
|
|
||||||
ObjectType = S7ObjectType.Organization,
|
|
||||||
Description = "Ciclo principal",
|
|
||||||
Modified = DateTime.Now.AddDays(-1)
|
|
||||||
},
|
|
||||||
new S7Block {
|
|
||||||
Name = "Clock_100ms",
|
|
||||||
Number = "OB35",
|
|
||||||
Size = 124,
|
|
||||||
ObjectType = S7ObjectType.Organization,
|
|
||||||
Description = "Interrupción cíclica 100ms",
|
|
||||||
Modified = DateTime.Now.AddDays(-7)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var ob in obs)
|
|
||||||
{
|
|
||||||
ob.Parent = obFolder;
|
|
||||||
obFolder.Children.Add(ob);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clase interna para la información de los dispositivos
|
|
||||||
private class DeviceIdInfo
|
|
||||||
{
|
|
||||||
public string Name { get; set; }
|
|
||||||
public int? SubblockListId { get; set; }
|
|
||||||
public int? SymbolListId { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 8.4 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 9.9 KiB |
|
@ -6,14 +6,50 @@
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<UseWPF>true</UseWPF>
|
<UseWPF>true</UseWPF>
|
||||||
|
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||||
|
<PackageReference Include="DotNetProjects.DotNetSiemensPLCToolBoxLibrary" Version="4.2.3" />
|
||||||
<PackageReference Include="Extended.Wpf.Toolkit" Version="4.7.25104.5739" />
|
<PackageReference Include="Extended.Wpf.Toolkit" Version="4.7.25104.5739" />
|
||||||
<PackageReference Include="NDbfReader" Version="2.4.0" />
|
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.1" />
|
<PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.1" />
|
||||||
|
<PackageReference Include="System.Text.Encoding.CodePages" Version="10.0.0-preview.2.25163.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Resources\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="Resources\db.png">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Resources\default.png">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Resources\device.png">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Resources\fb.png">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Resources\fc.png">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Resources\folder.png">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Resources\ob.png">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Resources\project.png">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Resources\symbol.png">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -0,0 +1,230 @@
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using S7Explorer.Models;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace S7Explorer.Services
|
||||||
|
{
|
||||||
|
public class ExportService
|
||||||
|
{
|
||||||
|
private readonly LogService _logService;
|
||||||
|
|
||||||
|
public ExportService()
|
||||||
|
{
|
||||||
|
_logService = LogService.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ExportProjectAsync(ProjectStructure project, ExportSettings settings)
|
||||||
|
{
|
||||||
|
return await Task.Run(() => ExportProject(project, settings));
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ExportProject(ProjectStructure project, ExportSettings settings)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logService.LogInfo($"Starting export to {settings.ExportPath}");
|
||||||
|
|
||||||
|
string exportContent = GenerateExportContent(project, settings);
|
||||||
|
|
||||||
|
// Ensure directory exists
|
||||||
|
string directory = Path.GetDirectoryName(settings.ExportPath) ?? string.Empty;
|
||||||
|
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write to file based on format
|
||||||
|
switch (settings.ExportFormat)
|
||||||
|
{
|
||||||
|
case ExportFormat.PlainText:
|
||||||
|
File.WriteAllText(settings.ExportPath, exportContent);
|
||||||
|
break;
|
||||||
|
case ExportFormat.MarkDown:
|
||||||
|
File.WriteAllText(settings.ExportPath, GenerateMarkdownContent(project, settings));
|
||||||
|
break;
|
||||||
|
case ExportFormat.HTML:
|
||||||
|
File.WriteAllText(settings.ExportPath, GenerateHtmlContent(project, settings));
|
||||||
|
break;
|
||||||
|
case ExportFormat.JSON:
|
||||||
|
File.WriteAllText(settings.ExportPath, GenerateJsonContent(project, settings));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logService.LogInfo($"Export completed successfully to {settings.ExportPath}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logService.LogError($"Error during export: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GenerateExportContent(ProjectStructure project, ExportSettings settings)
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
// Project info
|
||||||
|
sb.AppendLine("==========================================================");
|
||||||
|
sb.AppendLine($"PROJECT DOCUMENTATION: {project.Name}");
|
||||||
|
sb.AppendLine("==========================================================");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine(project.GetExportText());
|
||||||
|
|
||||||
|
// Hardware configuration
|
||||||
|
if (settings.ExportHardware)
|
||||||
|
{
|
||||||
|
sb.AppendLine("==========================================================");
|
||||||
|
sb.AppendLine("HARDWARE CONFIGURATION");
|
||||||
|
sb.AppendLine("==========================================================");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
ExportChildren(project.HardwareFolder, sb, settings, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Symbols
|
||||||
|
if (settings.ExportSymbols)
|
||||||
|
{
|
||||||
|
sb.AppendLine("==========================================================");
|
||||||
|
sb.AppendLine("SYMBOL TABLE");
|
||||||
|
sb.AppendLine("==========================================================");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
ExportChildren(project.SymbolsFolder, sb, settings, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blocks
|
||||||
|
if (settings.ExportBlocks)
|
||||||
|
{
|
||||||
|
sb.AppendLine("==========================================================");
|
||||||
|
sb.AppendLine("PROGRAM BLOCKS");
|
||||||
|
sb.AppendLine("==========================================================");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
ExportChildren(project.BlocksFolder, sb, settings, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExportChildren(ProjectItem item, StringBuilder sb, ExportSettings settings, int level)
|
||||||
|
{
|
||||||
|
// Don't export if no children
|
||||||
|
if (item.Children.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
string indent = new string(' ', level * 2);
|
||||||
|
|
||||||
|
foreach (var child in item.Children)
|
||||||
|
{
|
||||||
|
// Skip block code if settings say so
|
||||||
|
if (child is BlockItem blockItem && !settings.IncludeBlockCode)
|
||||||
|
{
|
||||||
|
string text = blockItem.GetExportText();
|
||||||
|
// Remove the block content section
|
||||||
|
int contentIndex = text.IndexOf("// Block Content");
|
||||||
|
if (contentIndex > 0)
|
||||||
|
{
|
||||||
|
text = text.Substring(0, contentIndex) + "// Block Content omitted per export settings\r\n\r\n";
|
||||||
|
}
|
||||||
|
sb.Append(indent);
|
||||||
|
sb.AppendLine(text.Replace("\r\n", "\r\n" + indent));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// For other item types, just get export text
|
||||||
|
sb.Append(indent);
|
||||||
|
sb.AppendLine(child.GetExportText().Replace("\r\n", "\r\n" + indent));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively export children
|
||||||
|
ExportChildren(child, sb, settings, level + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GenerateMarkdownContent(ProjectStructure project, ExportSettings settings)
|
||||||
|
{
|
||||||
|
// This would generate Markdown-formatted content
|
||||||
|
// Simplified implementation
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.AppendLine($"# PROJECT DOCUMENTATION: {project.Name}");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine($"**Project Path:** {project.ProjectPath}");
|
||||||
|
sb.AppendLine($"**Version:** {project.ProjectVersion}");
|
||||||
|
sb.AppendLine($"**Created:** {project.CreationDate}");
|
||||||
|
sb.AppendLine($"**Last Modified:** {project.LastModifiedDate}");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
// Rest of the implementation similar to plain text but with Markdown formatting
|
||||||
|
// ...
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GenerateHtmlContent(ProjectStructure project, ExportSettings settings)
|
||||||
|
{
|
||||||
|
// This would generate HTML-formatted content
|
||||||
|
// Simplified implementation
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.AppendLine("<!DOCTYPE html>");
|
||||||
|
sb.AppendLine("<html>");
|
||||||
|
sb.AppendLine("<head>");
|
||||||
|
sb.AppendLine($"<title>Project Documentation: {project.Name}</title>");
|
||||||
|
sb.AppendLine("<style>");
|
||||||
|
sb.AppendLine("body { font-family: Arial, sans-serif; margin: 20px; }");
|
||||||
|
sb.AppendLine("h1, h2 { color: #0066cc; }");
|
||||||
|
sb.AppendLine("pre { background-color: #f5f5f5; padding: 10px; border: 1px solid #ddd; }");
|
||||||
|
sb.AppendLine("</style>");
|
||||||
|
sb.AppendLine("</head>");
|
||||||
|
sb.AppendLine("<body>");
|
||||||
|
sb.AppendLine($"<h1>PROJECT DOCUMENTATION: {project.Name}</h1>");
|
||||||
|
|
||||||
|
// Rest of the implementation similar to plain text but with HTML formatting
|
||||||
|
// ...
|
||||||
|
|
||||||
|
sb.AppendLine("</body>");
|
||||||
|
sb.AppendLine("</html>");
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GenerateJsonContent(ProjectStructure project, ExportSettings settings)
|
||||||
|
{
|
||||||
|
// Create an object structure to export to JSON
|
||||||
|
var exportObject = new
|
||||||
|
{
|
||||||
|
Project = new
|
||||||
|
{
|
||||||
|
Name = project.Name,
|
||||||
|
Path = project.ProjectPath,
|
||||||
|
Version = project.ProjectVersion,
|
||||||
|
CreationDate = project.CreationDate,
|
||||||
|
LastModifiedDate = project.LastModifiedDate
|
||||||
|
},
|
||||||
|
Hardware = settings.ExportHardware ? GetExportObjectForItem(project.HardwareFolder) : null,
|
||||||
|
Symbols = settings.ExportSymbols ? GetExportObjectForItem(project.SymbolsFolder) : null,
|
||||||
|
Blocks = settings.ExportBlocks ? GetExportObjectForItem(project.BlocksFolder) : null
|
||||||
|
};
|
||||||
|
|
||||||
|
return JsonConvert.SerializeObject(exportObject, Formatting.Indented);
|
||||||
|
}
|
||||||
|
|
||||||
|
private object GetExportObjectForItem(ProjectItem item)
|
||||||
|
{
|
||||||
|
// Create a JSON-friendly object structure for an item and its children
|
||||||
|
var result = new
|
||||||
|
{
|
||||||
|
Name = item.Name,
|
||||||
|
Children = item.Children.Select(c => GetExportObjectForItem(c)).ToArray()
|
||||||
|
};
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace S7Explorer.Services
|
||||||
|
{
|
||||||
|
public class LogService
|
||||||
|
{
|
||||||
|
private static LogService? _instance;
|
||||||
|
private ObservableCollection<LogEntry> _logs = new();
|
||||||
|
|
||||||
|
// Singleton instance
|
||||||
|
public static LogService Instance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_instance == null)
|
||||||
|
_instance = new LogService();
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableCollection<LogEntry> Logs => _logs;
|
||||||
|
|
||||||
|
public void LogInfo(string message)
|
||||||
|
{
|
||||||
|
AddLog(LogLevel.Info, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LogWarning(string message)
|
||||||
|
{
|
||||||
|
AddLog(LogLevel.Warning, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LogError(string message)
|
||||||
|
{
|
||||||
|
AddLog(LogLevel.Error, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LogError(Exception ex)
|
||||||
|
{
|
||||||
|
AddLog(LogLevel.Error, $"Error: {ex.Message}");
|
||||||
|
AddLog(LogLevel.Debug, $"Stack trace: {ex.StackTrace}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LogDebug(string message)
|
||||||
|
{
|
||||||
|
AddLog(LogLevel.Debug, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddLog(LogLevel level, string message)
|
||||||
|
{
|
||||||
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
_logs.Add(new LogEntry
|
||||||
|
{
|
||||||
|
Timestamp = DateTime.Now,
|
||||||
|
Level = level,
|
||||||
|
Message = message
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,496 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using DotNetSiemensPLCToolBoxLibrary.DataTypes;
|
||||||
|
using DotNetSiemensPLCToolBoxLibrary.DataTypes.Blocks;
|
||||||
|
using DotNetSiemensPLCToolBoxLibrary.DataTypes.Blocks.Step7V5;
|
||||||
|
using DotNetSiemensPLCToolBoxLibrary.DataTypes.Projectfolders;
|
||||||
|
using DotNetSiemensPLCToolBoxLibrary.DataTypes.Projectfolders.Step7V5;
|
||||||
|
using DotNetSiemensPLCToolBoxLibrary.Projectfiles;
|
||||||
|
using S7Explorer.Models;
|
||||||
|
using ProjectFolder = DotNetSiemensPLCToolBoxLibrary.DataTypes.Projectfolders.ProjectFolder;
|
||||||
|
|
||||||
|
namespace S7Explorer.Services
|
||||||
|
{
|
||||||
|
public class ProjectService
|
||||||
|
{
|
||||||
|
private readonly LogService _logService;
|
||||||
|
|
||||||
|
public ProjectService()
|
||||||
|
{
|
||||||
|
_logService = LogService.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ProjectStructure?> LoadProjectAsync(string projectPath)
|
||||||
|
{
|
||||||
|
return await Task.Run(() => LoadProject(projectPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProjectStructure? LoadProject(string projectPath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logService.LogInfo($"Loading project from: {projectPath}");
|
||||||
|
|
||||||
|
if (!File.Exists(projectPath))
|
||||||
|
{
|
||||||
|
_logService.LogError($"Project file not found: {projectPath}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the Step7 project
|
||||||
|
var step7Project = Projects.GetStep7ProjectsFromDirectory(Path.GetDirectoryName(projectPath)).FirstOrDefault();
|
||||||
|
if (step7Project == null)
|
||||||
|
{
|
||||||
|
_logService.LogError("Could not load project. Make sure it's a valid S7 project file.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logService.LogInfo($"Project loaded: {step7Project.ProjectName}");
|
||||||
|
|
||||||
|
// Create the project structure
|
||||||
|
ProjectStructure projectStructure = new ProjectStructure
|
||||||
|
{
|
||||||
|
Name = step7Project.ProjectName,
|
||||||
|
ProjectData = step7Project,
|
||||||
|
ProjectPath = projectPath,
|
||||||
|
ProjectVersion = "Unknown", // Not directly available in the API
|
||||||
|
CreationDate = File.GetCreationTime(projectPath),
|
||||||
|
LastModifiedDate = File.GetLastWriteTime(projectPath)
|
||||||
|
};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Process the project structure
|
||||||
|
ProcessProjectStructure(step7Project, projectStructure);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logService.LogError($"Error processing project structure: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return projectStructure;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logService.LogError(ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessProjectStructure(Project project, ProjectStructure projectStructure)
|
||||||
|
{
|
||||||
|
// We need to carefully navigate the project structure
|
||||||
|
if (project.ProjectStructure == null)
|
||||||
|
{
|
||||||
|
_logService.LogWarning("Project structure is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use reflection to find and process PLC folders
|
||||||
|
ProcessProjectFolder(project.ProjectStructure, projectStructure);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessProjectFolder(ProjectFolder folder, ProjectStructure projectStructure)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Check if this is a CPU/PLC folder
|
||||||
|
if (IsProgrammFolder(folder))
|
||||||
|
{
|
||||||
|
string folderName = GetFolderName(folder);
|
||||||
|
_logService.LogInfo($"Processing PLC folder: {folderName}");
|
||||||
|
|
||||||
|
// Process this folder
|
||||||
|
LoadHardwareConfiguration(folder, projectStructure.HardwareFolder);
|
||||||
|
|
||||||
|
// Try to find blocks folder
|
||||||
|
var blocksFolder = GetBlocksFolder(folder);
|
||||||
|
if (blocksFolder != null)
|
||||||
|
{
|
||||||
|
LoadBlocks(blocksFolder, projectStructure.BlocksFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to find symbol table
|
||||||
|
var symbolTable = GetSymbolTable(folder);
|
||||||
|
if (symbolTable != null)
|
||||||
|
{
|
||||||
|
LoadSymbolTable(symbolTable, projectStructure.SymbolsFolder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to process sub-folders
|
||||||
|
ProcessSubFolders(folder, projectStructure);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logService.LogError($"Error processing folder: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessSubFolders(ProjectFolder folder, ProjectStructure projectStructure)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Use reflection to find sub-folders
|
||||||
|
var properties = folder.GetType().GetProperties();
|
||||||
|
foreach (var property in properties)
|
||||||
|
{
|
||||||
|
if (typeof(ProjectFolder).IsAssignableFrom(property.PropertyType))
|
||||||
|
{
|
||||||
|
// This property is a ProjectFolder
|
||||||
|
var subFolder = property.GetValue(folder) as ProjectFolder;
|
||||||
|
if (subFolder != null)
|
||||||
|
{
|
||||||
|
ProcessProjectFolder(subFolder, projectStructure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (property.PropertyType.IsGenericType &&
|
||||||
|
typeof(IEnumerable<>).IsAssignableFrom(property.PropertyType.GetGenericTypeDefinition()))
|
||||||
|
{
|
||||||
|
// This might be a collection of folders
|
||||||
|
var collection = property.GetValue(folder) as System.Collections.IEnumerable;
|
||||||
|
if (collection != null)
|
||||||
|
{
|
||||||
|
foreach (var item in collection)
|
||||||
|
{
|
||||||
|
if (item is ProjectFolder subFolder)
|
||||||
|
{
|
||||||
|
ProcessProjectFolder(subFolder, projectStructure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logService.LogError($"Error processing sub-folders: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadBlocks(IBlocksFolder blocksFolder, S7Explorer.Models.ProjectItem parentFolder)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// IBlocksFolder doesn't have a Name property directly, so let's use a generic name
|
||||||
|
string folderName = "Blocks";
|
||||||
|
if (blocksFolder is ProjectFolder pf && pf.GetType().GetProperty("Name") != null)
|
||||||
|
folderName = GetFolderName(pf);
|
||||||
|
|
||||||
|
_logService.LogInfo($"Loading blocks from: {folderName}");
|
||||||
|
|
||||||
|
// Create a folder for this block type
|
||||||
|
S7Explorer.Models.ProjectItem blocksFolderItem = new S7Explorer.Models.ProjectItem
|
||||||
|
{
|
||||||
|
Name = folderName,
|
||||||
|
Parent = parentFolder
|
||||||
|
};
|
||||||
|
parentFolder.Children.Add(blocksFolderItem);
|
||||||
|
|
||||||
|
// Get all blocks
|
||||||
|
var blockInfos = blocksFolder.BlockInfos;
|
||||||
|
_logService.LogInfo($"Found {blockInfos.Count} blocks");
|
||||||
|
|
||||||
|
// Add each block to the tree
|
||||||
|
foreach (var blockInfo in blockInfos)
|
||||||
|
{
|
||||||
|
// Skip system blocks if needed
|
||||||
|
if (blockInfo.BlockType == PLCBlockType.SFC ||
|
||||||
|
blockInfo.BlockType == PLCBlockType.SFB)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Load full block data
|
||||||
|
S7Block blockData = blocksFolder.GetBlock(blockInfo) as S7Block;
|
||||||
|
|
||||||
|
if (blockData != null)
|
||||||
|
{
|
||||||
|
// Need to extract block number from blockInfo
|
||||||
|
int blockNumber = 0;
|
||||||
|
|
||||||
|
// Try to parse block number from ToString() which typically returns something like "DB100"
|
||||||
|
string blockStr = blockInfo.ToString();
|
||||||
|
string numPart = string.Empty;
|
||||||
|
|
||||||
|
for (int i = 0; i < blockStr.Length; i++)
|
||||||
|
{
|
||||||
|
if (char.IsDigit(blockStr[i]))
|
||||||
|
numPart += blockStr[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(numPart))
|
||||||
|
int.TryParse(numPart, out blockNumber);
|
||||||
|
|
||||||
|
S7Explorer.Models.BlockItem blockItem = new S7Explorer.Models.BlockItem
|
||||||
|
{
|
||||||
|
Name = blockInfo.ToString(),
|
||||||
|
BlockInfo = blockInfo,
|
||||||
|
BlockData = blockData,
|
||||||
|
BlockType = blockInfo.BlockType.ToString(),
|
||||||
|
BlockNumber = blockNumber,
|
||||||
|
BlockComment = blockData.Title ?? string.Empty,
|
||||||
|
Parent = blocksFolderItem
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get block source code
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (blocksFolder is BlocksOfflineFolder bof)
|
||||||
|
{
|
||||||
|
var sourceCode = bof.GetSourceBlock(blockInfo, true);
|
||||||
|
blockItem.BlockContent = sourceCode ?? string.Empty;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
blockItem.BlockContent = "// Unable to retrieve block source code";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
blockItem.BlockContent = "// Unable to retrieve block source code";
|
||||||
|
}
|
||||||
|
|
||||||
|
blocksFolderItem.Children.Add(blockItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logService.LogWarning($"Failed to load block {blockInfo}: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logService.LogError($"Error loading blocks: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadSymbolTable(ISymbolTable symbolTable, S7Explorer.Models.ProjectItem parentFolder)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string tableName = "Symbols";
|
||||||
|
if (symbolTable is ProjectFolder pf)
|
||||||
|
tableName = GetFolderName(pf);
|
||||||
|
|
||||||
|
_logService.LogInfo($"Loading symbols from table: {tableName}");
|
||||||
|
|
||||||
|
// Create a folder for this symbol table
|
||||||
|
S7Explorer.Models.ProjectItem symbolTableItem = new S7Explorer.Models.ProjectItem
|
||||||
|
{
|
||||||
|
Name = tableName,
|
||||||
|
Parent = parentFolder
|
||||||
|
};
|
||||||
|
parentFolder.Children.Add(symbolTableItem);
|
||||||
|
|
||||||
|
// Try to get symbols using reflection to avoid direct property access
|
||||||
|
int count = LoadSymbolsUsingReflection(symbolTable, symbolTableItem);
|
||||||
|
|
||||||
|
if (count > 0)
|
||||||
|
{
|
||||||
|
_logService.LogInfo($"Loaded {count} symbols");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logService.LogWarning("No symbols found or symbol table is empty");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logService.LogError($"Error loading symbols: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int LoadSymbolsUsingReflection(ISymbolTable symbolTable, S7Explorer.Models.ProjectItem parentFolder)
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Find property that might contain symbols
|
||||||
|
var properties = symbolTable.GetType().GetProperties();
|
||||||
|
|
||||||
|
// Look for collection properties that might be symbol collections
|
||||||
|
foreach (var prop in properties)
|
||||||
|
{
|
||||||
|
// Skip non-collection properties
|
||||||
|
if (!typeof(IEnumerable).IsAssignableFrom(prop.PropertyType) ||
|
||||||
|
prop.PropertyType == typeof(string))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Try to get the collection
|
||||||
|
var collection = prop.GetValue(symbolTable) as IEnumerable;
|
||||||
|
if (collection == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Check if this collection contains symbols
|
||||||
|
foreach (var item in collection)
|
||||||
|
{
|
||||||
|
if (item == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Check if this looks like a symbol
|
||||||
|
var itemType = item.GetType();
|
||||||
|
var symbolProp = itemType.GetProperty("Symbol");
|
||||||
|
var operandProp = itemType.GetProperty("Operand") ?? itemType.GetProperty("Address");
|
||||||
|
|
||||||
|
// If it has Symbol and Operand properties, treat it as a symbol
|
||||||
|
if (symbolProp != null && operandProp != null)
|
||||||
|
{
|
||||||
|
var dataTypeProp = itemType.GetProperty("DataType");
|
||||||
|
var commentProp = itemType.GetProperty("Comment");
|
||||||
|
|
||||||
|
S7Explorer.Models.SymbolItem symbolItem = new S7Explorer.Models.SymbolItem
|
||||||
|
{
|
||||||
|
Name = symbolProp.GetValue(item)?.ToString() ?? "Unknown",
|
||||||
|
SymbolAddress = operandProp.GetValue(item)?.ToString() ?? "",
|
||||||
|
SymbolDataType = dataTypeProp?.GetValue(item)?.ToString() ?? "",
|
||||||
|
SymbolComment = commentProp?.GetValue(item)?.ToString() ?? "",
|
||||||
|
Parent = parentFolder
|
||||||
|
};
|
||||||
|
|
||||||
|
parentFolder.Children.Add(symbolItem);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we found symbols, stop looking through properties
|
||||||
|
if (count > 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logService.LogError($"Error loading symbols using reflection: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadHardwareConfiguration(object cpuFolder, S7Explorer.Models.ProjectItem parentFolder)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string folderName = GetFolderName(cpuFolder);
|
||||||
|
_logService.LogInfo($"Loading hardware configuration for CPU: {folderName}");
|
||||||
|
|
||||||
|
// Create a folder for this CPU
|
||||||
|
S7Explorer.Models.ProjectItem cpuItem = new S7Explorer.Models.ProjectItem
|
||||||
|
{
|
||||||
|
Name = folderName,
|
||||||
|
Parent = parentFolder
|
||||||
|
};
|
||||||
|
parentFolder.Children.Add(cpuItem);
|
||||||
|
|
||||||
|
// Add CPU information
|
||||||
|
S7Explorer.Models.HardwareItem cpuHardwareItem = new S7Explorer.Models.HardwareItem
|
||||||
|
{
|
||||||
|
Name = $"CPU {folderName}",
|
||||||
|
ModuleType = "CPU",
|
||||||
|
OrderNumber = "Unknown", // Would need to extract from hardware
|
||||||
|
Position = "Unknown",
|
||||||
|
Parent = cpuItem
|
||||||
|
};
|
||||||
|
cpuItem.Children.Add(cpuHardwareItem);
|
||||||
|
|
||||||
|
// We would need to analyze the hardware configuration in more detail
|
||||||
|
// to extract modules, but this requires more insight into the library's structure
|
||||||
|
|
||||||
|
_logService.LogInfo("Hardware configuration loaded");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logService.LogError($"Error loading hardware configuration: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper methods to safely access project structure
|
||||||
|
|
||||||
|
private bool IsProgrammFolder(object folder)
|
||||||
|
{
|
||||||
|
return folder is IProgrammFolder ||
|
||||||
|
folder.GetType().GetInterfaces().Any(i => i.Name == "IProgrammFolder");
|
||||||
|
}
|
||||||
|
|
||||||
|
private IBlocksFolder GetBlocksFolder(object folder)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Try using reflection to access the BlocksOfflineFolder property
|
||||||
|
var property = folder.GetType().GetProperty("BlocksOfflineFolder");
|
||||||
|
if (property != null)
|
||||||
|
{
|
||||||
|
return property.GetValue(folder) as IBlocksFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If that fails, look for a property of type IBlocksFolder
|
||||||
|
foreach (var prop in folder.GetType().GetProperties())
|
||||||
|
{
|
||||||
|
if (typeof(IBlocksFolder).IsAssignableFrom(prop.PropertyType))
|
||||||
|
{
|
||||||
|
return prop.GetValue(folder) as IBlocksFolder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logService.LogError($"Error accessing blocks folder: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ISymbolTable GetSymbolTable(object folder)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Try using reflection to access the SymbolTable property
|
||||||
|
var property = folder.GetType().GetProperty("SymbolTable");
|
||||||
|
if (property != null)
|
||||||
|
{
|
||||||
|
return property.GetValue(folder) as ISymbolTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If that fails, look for a property of type ISymbolTable
|
||||||
|
foreach (var prop in folder.GetType().GetProperties())
|
||||||
|
{
|
||||||
|
if (typeof(ISymbolTable).IsAssignableFrom(prop.PropertyType))
|
||||||
|
{
|
||||||
|
return prop.GetValue(folder) as ISymbolTable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logService.LogError($"Error accessing symbol table: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetFolderName(object folder)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Try to access Name property
|
||||||
|
var property = folder.GetType().GetProperty("Name");
|
||||||
|
if (property != null)
|
||||||
|
{
|
||||||
|
return property.GetValue(folder)?.ToString() ?? "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Ignore errors
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,154 +1,141 @@
|
||||||
using System;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Input;
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using Ookii.Dialogs.Wpf;
|
using Ookii.Dialogs.Wpf;
|
||||||
|
using S7Explorer.Helpers;
|
||||||
using S7Explorer.Models;
|
using S7Explorer.Models;
|
||||||
using S7Explorer.Parsers;
|
using S7Explorer.Services;
|
||||||
|
using S7Explorer.Services;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
namespace S7Explorer.ViewModels
|
namespace S7Explorer.ViewModels
|
||||||
{
|
{
|
||||||
public class MainViewModel : ObservableObject
|
public partial class MainViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
private string _searchText;
|
private readonly ProjectService _projectService;
|
||||||
private object _selectedObject;
|
private readonly ExportService _exportService;
|
||||||
private object _selectedTreeItem;
|
private readonly SearchHelper _searchHelper;
|
||||||
private S7Project _currentProject;
|
|
||||||
private ObservableCollection<S7Object> _projectStructure;
|
[ObservableProperty]
|
||||||
private string _projectInfo;
|
private ProjectStructure? _projectStructure;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private ProjectItem? _selectedItem;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private object? _selectedItemDetails;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _projectPath = string.Empty;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isProjectLoaded;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _searchText = string.Empty;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isCaseSensitive;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _useRegex;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private ObservableCollection<LogEntry> _logEntries = new();
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private ExportSettings _exportSettings = new();
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
private bool _isLoading;
|
private bool _isLoading;
|
||||||
|
|
||||||
public string SearchText
|
|
||||||
{
|
|
||||||
get => _searchText;
|
|
||||||
set => SetProperty(ref _searchText, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public object SelectedObject
|
|
||||||
{
|
|
||||||
get => _selectedObject;
|
|
||||||
set => SetProperty(ref _selectedObject, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public object SelectedTreeItem
|
|
||||||
{
|
|
||||||
get => _selectedTreeItem;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (SetProperty(ref _selectedTreeItem, value))
|
|
||||||
{
|
|
||||||
// Actualizar el objeto seleccionado para PropertyGrid
|
|
||||||
SelectedObject = _selectedTreeItem;
|
|
||||||
|
|
||||||
// Acciones específicas según el tipo de objeto seleccionado
|
|
||||||
if (_selectedTreeItem is S7Function fc)
|
|
||||||
{
|
|
||||||
// Si el objeto seleccionado es una función, actualizar su interfaz
|
|
||||||
UpdateFunctionInterface(fc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Actualiza la información de interfaz de una función seleccionada
|
|
||||||
/// </summary>
|
|
||||||
private void UpdateFunctionInterface(S7Function fc)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Aquí podríamos cargar información adicional específica de la función
|
|
||||||
// Por ejemplo, cargar el código de la función, detalles de parámetros, etc.
|
|
||||||
|
|
||||||
// Actualizar estadísticas o análisis de la función
|
|
||||||
// Por ejemplo, mostrar uso de la función en otros bloques
|
|
||||||
|
|
||||||
// Este método se ejecutará cuando el usuario seleccione una FC en el árbol
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
// Ignorar errores para no interrumpir la experiencia del usuario
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObservableCollection<S7Object> ProjectStructure
|
|
||||||
{
|
|
||||||
get => _projectStructure;
|
|
||||||
set => SetProperty(ref _projectStructure, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ProjectInfo
|
|
||||||
{
|
|
||||||
get => _projectInfo;
|
|
||||||
set => SetProperty(ref _projectInfo, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsLoading
|
|
||||||
{
|
|
||||||
get => _isLoading;
|
|
||||||
set => SetProperty(ref _isLoading, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IRelayCommand OpenProjectCommand { get; }
|
|
||||||
public IRelayCommand SearchCommand { get; }
|
|
||||||
|
|
||||||
public MainViewModel()
|
public MainViewModel()
|
||||||
{
|
{
|
||||||
ProjectStructure = new ObservableCollection<S7Object>();
|
_projectService = new ProjectService();
|
||||||
OpenProjectCommand = new RelayCommand(OpenProject);
|
_exportService = new ExportService();
|
||||||
SearchCommand = new RelayCommand(SearchInProject);
|
_searchHelper = new SearchHelper();
|
||||||
ProjectInfo = "No hay proyecto abierto";
|
|
||||||
|
// Initialize log entries collection
|
||||||
|
LogEntries = LogService.Instance.Logs;
|
||||||
|
|
||||||
|
// Initialize export settings
|
||||||
|
ExportSettings.ExportPath = Path.Combine(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
|
||||||
|
"S7Documentation.txt");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OpenProject()
|
partial void OnSelectedItemChanged(ProjectItem? value)
|
||||||
{
|
{
|
||||||
var dialog = new VistaOpenFileDialog
|
SelectedItemDetails = value?.GetDetailsObject();
|
||||||
{
|
}
|
||||||
Title = "Seleccionar archivo de proyecto STEP7",
|
|
||||||
Filter = "Proyectos STEP7 (*.s7p)|*.s7p|Todos los archivos (*.*)|*.*",
|
|
||||||
CheckFileExists = true
|
|
||||||
};
|
|
||||||
|
|
||||||
if (dialog.ShowDialog() == true)
|
[RelayCommand]
|
||||||
|
private async Task LoadProject()
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
LoadProjectAsync(dialog.FileName);
|
var dialog = new VistaOpenFileDialog
|
||||||
|
{
|
||||||
|
Title = "Open Siemens S7 Project",
|
||||||
|
Filter = "S7 Projects (*.s7p)|*.s7p|All files (*.*)|*.*",
|
||||||
|
CheckFileExists = true
|
||||||
|
};
|
||||||
|
|
||||||
|
if (dialog.ShowDialog() == true)
|
||||||
|
{
|
||||||
|
ProjectPath = dialog.FileName;
|
||||||
|
await LoadProjectFromPath(ProjectPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logService.LogError(ex);
|
||||||
|
MessageBox.Show($"Error loading project: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void LoadProjectAsync(string filePath)
|
[RelayCommand]
|
||||||
|
private async Task ReloadProject()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(ProjectPath) || !File.Exists(ProjectPath))
|
||||||
|
{
|
||||||
|
_logService.LogWarning("No project loaded to reload");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await LoadProjectFromPath(ProjectPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadProjectFromPath(string path)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
IsLoading = true;
|
IsLoading = true;
|
||||||
ProjectInfo = "Cargando proyecto...";
|
|
||||||
|
|
||||||
// Reiniciar estado
|
// Load the project
|
||||||
ProjectStructure.Clear();
|
_logService.LogInfo($"Loading project from {path}");
|
||||||
_currentProject = null;
|
ProjectStructure = await _projectService.LoadProjectAsync(path);
|
||||||
|
|
||||||
// Parsear proyecto
|
if (ProjectStructure != null)
|
||||||
var parser = new S7ProjectParser(filePath);
|
{
|
||||||
_currentProject = await parser.ParseProjectAsync();
|
IsProjectLoaded = true;
|
||||||
|
_logService.LogInfo("Project loaded successfully");
|
||||||
// Actualizar UI
|
}
|
||||||
ProjectStructure.Add(_currentProject);
|
else
|
||||||
_currentProject.IsExpanded = true;
|
{
|
||||||
|
IsProjectLoaded = false;
|
||||||
// Actualizar info
|
_logService.LogError("Failed to load project");
|
||||||
ProjectInfo = $"Proyecto: {Path.GetFileNameWithoutExtension(filePath)}";
|
MessageBox.Show("Failed to load project", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
MessageBox.Show($"Error al cargar el proyecto: {ex.Message}", "Error",
|
_logService.LogError(ex);
|
||||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
MessageBox.Show($"Error loading project: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
ProjectInfo = "Error al cargar el proyecto";
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
@ -156,125 +143,166 @@ namespace S7Explorer.ViewModels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SearchInProject()
|
[RelayCommand]
|
||||||
|
private void Search()
|
||||||
{
|
{
|
||||||
if (_currentProject == null || string.IsNullOrWhiteSpace(SearchText))
|
if (!IsProjectLoaded || ProjectStructure == null)
|
||||||
|
{
|
||||||
|
_logService.LogWarning("No project loaded to search");
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(SearchText))
|
||||||
|
{
|
||||||
|
_logService.LogWarning("No search text specified");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Recopilar todos los objetos en el proyecto
|
var results = _searchHelper.Search(ProjectStructure, SearchText, IsCaseSensitive, UseRegex);
|
||||||
var allObjects = GetAllObjects(_currentProject);
|
|
||||||
|
|
||||||
// Lista para almacenar coincidencias
|
if (results.Count == 0)
|
||||||
var matchingObjects = new List<S7Object>();
|
|
||||||
|
|
||||||
// Buscar en función del texto de búsqueda
|
|
||||||
string searchText = SearchText.Trim().ToLowerInvariant();
|
|
||||||
|
|
||||||
// Buscar objetos que coincidan con el texto
|
|
||||||
foreach (var obj in allObjects)
|
|
||||||
{
|
{
|
||||||
// Comprobar si el texto de búsqueda parece ser una referencia a FC específica
|
_logService.LogInfo($"No results found for '{SearchText}'");
|
||||||
if (searchText.StartsWith("fc") && searchText.Length >= 3)
|
MessageBox.Show($"No results found for '{SearchText}'", "Search Results", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||||
{
|
|
||||||
// Si es una función específica (por ejemplo "fc5")
|
|
||||||
if (obj is S7Function func &&
|
|
||||||
func.Number.ToLowerInvariant() == searchText)
|
|
||||||
{
|
|
||||||
matchingObjects.Add(func);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Comprobar si queremos encontrar todas las FCs
|
|
||||||
else if (searchText == "fc" || searchText == "función" || searchText == "funcion")
|
|
||||||
{
|
|
||||||
if (obj is S7Function)
|
|
||||||
{
|
|
||||||
matchingObjects.Add(obj);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Búsqueda en parámetros de función
|
|
||||||
else if (obj is S7Function func)
|
|
||||||
{
|
|
||||||
if (func.ContainsText(searchText))
|
|
||||||
{
|
|
||||||
matchingObjects.Add(func);
|
|
||||||
}
|
|
||||||
else if (func.Parameters != null)
|
|
||||||
{
|
|
||||||
// Buscar en los parámetros de la función
|
|
||||||
foreach (var param in func.Parameters)
|
|
||||||
{
|
|
||||||
if (param.Name.ToLowerInvariant().Contains(searchText) ||
|
|
||||||
param.DataType.ToLowerInvariant().Contains(searchText) ||
|
|
||||||
(param.Description?.ToLowerInvariant().Contains(searchText) ?? false))
|
|
||||||
{
|
|
||||||
matchingObjects.Add(func);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Búsqueda general para otros objetos
|
|
||||||
else if (obj.ContainsText(searchText))
|
|
||||||
{
|
|
||||||
matchingObjects.Add(obj);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matchingObjects.Count == 0)
|
|
||||||
{
|
|
||||||
MessageBox.Show($"No se encontraron coincidencias para: {SearchText}",
|
|
||||||
"Búsqueda", MessageBoxButton.OK, MessageBoxImage.Information);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seleccionar el primer objeto coincidente y expandir su ruta
|
// Show search results dialog
|
||||||
var firstMatch = matchingObjects.First();
|
ShowSearchResults(results);
|
||||||
SelectAndExpandToObject(firstMatch);
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logService.LogError(ex);
|
||||||
|
MessageBox.Show($"Error during search: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Informar al usuario
|
private void ShowSearchResults(List<SearchResult> results)
|
||||||
if (matchingObjects.Count > 1)
|
{
|
||||||
|
// In a full implementation, this would show a dialog or window with search results
|
||||||
|
// For demonstration, we'll just select the first result and expand to it
|
||||||
|
if (results.Count > 0)
|
||||||
|
{
|
||||||
|
var firstResult = results[0];
|
||||||
|
TreeViewHelper.ExpandToItem(firstResult.Item);
|
||||||
|
SelectedItem = firstResult.Item;
|
||||||
|
|
||||||
|
_logService.LogInfo($"Found {results.Count} results, highlighted first result");
|
||||||
|
MessageBox.Show($"Found {results.Count} results", "Search Results", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task ExportDocumentation()
|
||||||
|
{
|
||||||
|
if (!IsProjectLoaded || ProjectStructure == null)
|
||||||
|
{
|
||||||
|
_logService.LogWarning("No project loaded to export");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Show export settings dialog
|
||||||
|
bool proceed = ShowExportSettings();
|
||||||
|
|
||||||
|
if (!proceed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
IsLoading = true;
|
||||||
|
|
||||||
|
// Export the documentation
|
||||||
|
bool success = await _exportService.ExportProjectAsync(ProjectStructure, ExportSettings);
|
||||||
|
|
||||||
|
if (success)
|
||||||
{
|
{
|
||||||
MessageBox.Show($"Se encontraron {matchingObjects.Count} coincidencias. " +
|
_logService.LogInfo($"Documentation exported to {ExportSettings.ExportPath}");
|
||||||
"Mostrando la primera coincidencia.",
|
MessageBox.Show($"Documentation exported to {ExportSettings.ExportPath}", "Export Complete", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||||
"Búsqueda", MessageBoxButton.OK, MessageBoxImage.Information);
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logService.LogError("Failed to export documentation");
|
||||||
|
MessageBox.Show("Failed to export documentation", "Export Failed", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
MessageBox.Show($"Error durante la búsqueda: {ex.Message}",
|
_logService.LogError(ex);
|
||||||
"Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
MessageBox.Show($"Error exporting documentation: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsLoading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<S7Object> GetAllObjects(S7Object root)
|
private bool ShowExportSettings()
|
||||||
{
|
{
|
||||||
// Devolver el objeto raíz
|
// Show file save dialog for export path
|
||||||
yield return root;
|
var dialog = new VistaSaveFileDialog
|
||||||
|
|
||||||
// Recursivamente devolver todos los hijos
|
|
||||||
foreach (var child in root.Children)
|
|
||||||
{
|
{
|
||||||
foreach (var obj in GetAllObjects(child))
|
Title = "Export Documentation",
|
||||||
|
Filter = GetExportFilter(),
|
||||||
|
FileName = Path.GetFileName(ExportSettings.ExportPath),
|
||||||
|
InitialDirectory = Path.GetDirectoryName(ExportSettings.ExportPath)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (dialog.ShowDialog() == true)
|
||||||
|
{
|
||||||
|
ExportSettings.ExportPath = dialog.FileName;
|
||||||
|
|
||||||
|
// Determine export format from file extension
|
||||||
|
string extension = Path.GetExtension(dialog.FileName).ToLower();
|
||||||
|
ExportSettings.ExportFormat = extension switch
|
||||||
{
|
{
|
||||||
yield return obj;
|
".md" => ExportFormat.MarkDown,
|
||||||
}
|
".html" => ExportFormat.HTML,
|
||||||
|
".json" => ExportFormat.JSON,
|
||||||
|
_ => ExportFormat.PlainText
|
||||||
|
};
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetExportFilter()
|
||||||
|
{
|
||||||
|
return "Text Files (*.txt)|*.txt|" +
|
||||||
|
"Markdown Files (*.md)|*.md|" +
|
||||||
|
"HTML Files (*.html)|*.html|" +
|
||||||
|
"JSON Files (*.json)|*.json|" +
|
||||||
|
"All Files (*.*)|*.*";
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void ExpandAll()
|
||||||
|
{
|
||||||
|
if (ProjectStructure != null)
|
||||||
|
{
|
||||||
|
TreeViewHelper.ExpandAll(ProjectStructure);
|
||||||
|
_logService.LogInfo("Expanded all tree nodes");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SelectAndExpandToObject(S7Object obj)
|
[RelayCommand]
|
||||||
|
private void CollapseAll()
|
||||||
{
|
{
|
||||||
// Expandir todos los nodos padres hasta llegar al objeto
|
if (ProjectStructure != null)
|
||||||
var parent = obj.Parent;
|
|
||||||
while (parent != null)
|
|
||||||
{
|
{
|
||||||
parent.IsExpanded = true;
|
TreeViewHelper.CollapseAll(ProjectStructure);
|
||||||
parent = parent.Parent;
|
_logService.LogInfo("Collapsed all tree nodes");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Seleccionar el objeto
|
[RelayCommand]
|
||||||
SelectedTreeItem = obj;
|
private void ClearLog()
|
||||||
|
{
|
||||||
|
LogEntries.Clear();
|
||||||
|
_logService.LogInfo("Log cleared");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,12 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace S7Explorer.ViewModels
|
|
||||||
{
|
|
||||||
class S7ObjectViewModel
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using S7Explorer.Services;
|
||||||
|
|
||||||
|
namespace S7Explorer.ViewModels
|
||||||
|
{
|
||||||
|
public class ViewModelBase : ObservableObject
|
||||||
|
{
|
||||||
|
protected readonly LogService _logService;
|
||||||
|
|
||||||
|
public ViewModelBase()
|
||||||
|
{
|
||||||
|
_logService = LogService.Instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,67 +0,0 @@
|
||||||
<Window x:Class="S7Explorer.Views.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit" xmlns:local="clr-namespace:S7Explorer" mc:Ignorable="d"
|
|
||||||
Title="S7 Project Explorer" Height="600" Width="900">
|
|
||||||
<Grid>
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="*" />
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
|
|
||||||
<!-- Toolbar -->
|
|
||||||
<Grid Grid.Row="0" Margin="8">
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
<ColumnDefinition Width="*" />
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
|
|
||||||
<Button Grid.Column="0" Content="Abrir Proyecto" Command="{Binding OpenProjectCommand}" Padding="8,4"
|
|
||||||
Margin="0,0,8,0" />
|
|
||||||
|
|
||||||
<TextBox Grid.Column="1" Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}" Padding="4"
|
|
||||||
Margin="0,0,8,0" VerticalContentAlignment="Center" KeyDown="SearchBox_KeyDown" />
|
|
||||||
|
|
||||||
<Button Grid.Column="2" Content="Buscar" Command="{Binding SearchCommand}" Padding="8,4" Margin="0,0,8,0" />
|
|
||||||
|
|
||||||
<TextBlock Grid.Column="3" Text="{Binding ProjectInfo}" VerticalAlignment="Center" />
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<!-- Main Content -->
|
|
||||||
<Grid Grid.Row="1">
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="300" MinWidth="200" />
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
<ColumnDefinition Width="*" />
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
|
|
||||||
<!-- Project Structure TreeView -->
|
|
||||||
<TreeView Grid.Column="0" Margin="8,0,0,8" ItemsSource="{Binding ProjectStructure}"
|
|
||||||
SelectedItemChanged="TreeView_SelectedItemChanged">
|
|
||||||
<TreeView.ItemTemplate>
|
|
||||||
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
|
|
||||||
<StackPanel Orientation="Horizontal">
|
|
||||||
<Image Source="{Binding IconSource}" Width="16" Height="16" Margin="0,0,4,0" />
|
|
||||||
<TextBlock Text="{Binding Name}" />
|
|
||||||
</StackPanel>
|
|
||||||
</HierarchicalDataTemplate>
|
|
||||||
</TreeView.ItemTemplate>
|
|
||||||
<TreeView.ItemContainerStyle>
|
|
||||||
<Style TargetType="TreeViewItem">
|
|
||||||
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
|
|
||||||
</Style>
|
|
||||||
</TreeView.ItemContainerStyle>
|
|
||||||
</TreeView>
|
|
||||||
|
|
||||||
<!-- Splitter -->
|
|
||||||
<GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Center" VerticalAlignment="Stretch" />
|
|
||||||
|
|
||||||
<!-- Property Grid -->
|
|
||||||
<xctk:PropertyGrid Grid.Column="2" Margin="0,0,8,8" SelectedObject="{Binding SelectedObject}"
|
|
||||||
AutoGenerateProperties="True" ShowSearchBox="True" ShowSortOptions="True" ShowTitle="True" />
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</Window>
|
|
|
@ -1,37 +0,0 @@
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Controls;
|
|
||||||
using System.Windows.Input;
|
|
||||||
using S7Explorer.ViewModels;
|
|
||||||
|
|
||||||
namespace S7Explorer.Views
|
|
||||||
{
|
|
||||||
public partial class MainWindow : Window
|
|
||||||
{
|
|
||||||
private MainViewModel ViewModel => (MainViewModel)DataContext;
|
|
||||||
|
|
||||||
public MainWindow()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
DataContext = new MainViewModel();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
|
|
||||||
{
|
|
||||||
if (ViewModel != null)
|
|
||||||
{
|
|
||||||
ViewModel.SelectedTreeItem = e.NewValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void SearchBox_KeyDown(object sender, KeyEventArgs e)
|
|
||||||
{
|
|
||||||
if (e.Key == Key.Enter && ViewModel != null)
|
|
||||||
{
|
|
||||||
ViewModel.SearchCommand.Execute(null);
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|