Compare commits
No commits in common. "376f7f6cf6fce4d93d9210ae03e1973511bf7169" and "ea96ba2bb962964fd2c8baeb8eba30cd018338bf" have entirely different histories.
376f7f6cf6
...
ea96ba2bb9
18
App.xaml
|
@ -1,17 +1,11 @@
|
||||||
<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="MainWindow.xaml">
|
StartupUri="Views/MainWindow.xaml">
|
||||||
<Application.Resources>
|
<Application.Resources>
|
||||||
<!-- Define the application resources -->
|
<ResourceDictionary>
|
||||||
<Style x:Key="ToolbarButtonStyle" TargetType="Button">
|
<ResourceDictionary.MergedDictionaries>
|
||||||
<Setter Property="Margin" Value="5" />
|
<!-- Aquí puedes incluir estilos globales -->
|
||||||
<Setter Property="Padding" Value="8,3" />
|
</ResourceDictionary.MergedDictionaries>
|
||||||
<Setter Property="MinWidth" Value="80" />
|
</ResourceDictionary>
|
||||||
</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,8 +1,88 @@
|
||||||
using System.Windows;
|
using System;
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,143 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,98 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
126
MainWindow.xaml
|
@ -1,126 +0,0 @@
|
||||||
<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>
|
|
|
@ -1,96 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,150 @@
|
||||||
|
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,49 +1,169 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.ComponentModel;
|
||||||
using DotNetSiemensPLCToolBoxLibrary.DataTypes.Blocks.Step7V5;
|
using System.Linq;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using S7Explorer.Parsers;
|
||||||
|
|
||||||
namespace S7Explorer.Models
|
namespace S7Explorer.Models
|
||||||
{
|
{
|
||||||
public class S7Function
|
public class S7Function : S7Block
|
||||||
{
|
{
|
||||||
public S7Block Block { get; set; }
|
private string _returnType;
|
||||||
public string Name { get; set; }
|
private List<FunctionParameter> _parameters;
|
||||||
public string Description { get; set; }
|
private string _interface;
|
||||||
public List<S7FunctionParameter> Parameters { get; set; } = new List<S7FunctionParameter>();
|
|
||||||
public string Code { get; set; }
|
[DisplayName("Tipo de Retorno")]
|
||||||
|
[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>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public S7Function(S7Block block)
|
/// <summary>
|
||||||
|
/// Actualiza la representación formateada de la interfaz basada en los parámetros
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateFormattedInterface()
|
||||||
{
|
{
|
||||||
Block = block;
|
if (Parameters == null || Parameters.Count == 0)
|
||||||
Name = block?.Name ?? string.Empty;
|
{
|
||||||
Description = block?.Title ?? string.Empty;
|
Interface = "// No hay parámetros definidos";
|
||||||
|
return;
|
||||||
// Code would need to be extracted from the block
|
|
||||||
// This is just a placeholder
|
|
||||||
Code = "// Function code would be extracted here";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
var builder = new System.Text.StringBuilder();
|
||||||
|
|
||||||
|
// Agrupar por dirección
|
||||||
|
var inputParams = Parameters.Where(p => p.Direction == "IN").ToList();
|
||||||
|
var outputParams = Parameters.Where(p => p.Direction == "OUT").ToList();
|
||||||
|
var inOutParams = Parameters.Where(p => p.Direction == "IN/OUT").ToList();
|
||||||
|
|
||||||
|
// Añadir tipo de retorno
|
||||||
|
builder.AppendLine($"FUNCTION {Name} : {ReturnType ?? "VOID"}");
|
||||||
|
builder.AppendLine();
|
||||||
|
|
||||||
|
// Añadir parámetros de entrada
|
||||||
|
if (inputParams.Any())
|
||||||
{
|
{
|
||||||
return Name;
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class S7FunctionParameter
|
// Añadir parámetros de salida
|
||||||
|
if (outputParams.Any())
|
||||||
{
|
{
|
||||||
public string Name { get; set; }
|
builder.AppendLine("VAR_OUTPUT");
|
||||||
public string DataType { get; set; }
|
foreach (var param in outputParams)
|
||||||
public string Comment { get; set; }
|
{
|
||||||
public string Direction { get; set; } // IN, OUT, IN_OUT
|
builder.Append($" {param.Name} : {param.DataType}");
|
||||||
|
if (!string.IsNullOrEmpty(param.Description))
|
||||||
|
builder.Append($"; // {param.Description}");
|
||||||
|
builder.AppendLine();
|
||||||
|
}
|
||||||
|
builder.AppendLine("END_VAR");
|
||||||
|
builder.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
public override string ToString()
|
// Añadir parámetros de entrada/salida
|
||||||
|
if (inOutParams.Any())
|
||||||
{
|
{
|
||||||
return $"{Direction} {Name} : {DataType}";
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,37 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,139 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,439 @@
|
||||||
|
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" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,412 @@
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
BIN
Resources/db.png
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 13 KiB |
BIN
Resources/fb.png
Before Width: | Height: | Size: 25 KiB |
BIN
Resources/fc.png
Before Width: | Height: | Size: 7.4 KiB |
Before Width: | Height: | Size: 8.4 KiB |
BIN
Resources/ob.png
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 9.9 KiB |
|
@ -6,50 +6,14 @@
|
||||||
<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>
|
||||||
|
|
|
@ -1,230 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
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
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,496 +0,0 @@
|
||||||
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,141 +1,154 @@
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using System;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
|
||||||
using Ookii.Dialogs.Wpf;
|
|
||||||
using S7Explorer.Helpers;
|
|
||||||
using S7Explorer.Models;
|
|
||||||
using S7Explorer.Services;
|
|
||||||
using S7Explorer.Services;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using Ookii.Dialogs.Wpf;
|
||||||
|
using S7Explorer.Models;
|
||||||
|
using S7Explorer.Parsers;
|
||||||
|
|
||||||
namespace S7Explorer.ViewModels
|
namespace S7Explorer.ViewModels
|
||||||
{
|
{
|
||||||
public partial class MainViewModel : ViewModelBase
|
public class MainViewModel : ObservableObject
|
||||||
{
|
{
|
||||||
private readonly ProjectService _projectService;
|
private string _searchText;
|
||||||
private readonly ExportService _exportService;
|
private object _selectedObject;
|
||||||
private readonly SearchHelper _searchHelper;
|
private object _selectedTreeItem;
|
||||||
|
private S7Project _currentProject;
|
||||||
[ObservableProperty]
|
private ObservableCollection<S7Object> _projectStructure;
|
||||||
private ProjectStructure? _projectStructure;
|
private string _projectInfo;
|
||||||
|
|
||||||
[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 MainViewModel()
|
public string SearchText
|
||||||
{
|
{
|
||||||
_projectService = new ProjectService();
|
get => _searchText;
|
||||||
_exportService = new ExportService();
|
set => SetProperty(ref _searchText, value);
|
||||||
_searchHelper = new SearchHelper();
|
|
||||||
|
|
||||||
// Initialize log entries collection
|
|
||||||
LogEntries = LogService.Instance.Logs;
|
|
||||||
|
|
||||||
// Initialize export settings
|
|
||||||
ExportSettings.ExportPath = Path.Combine(
|
|
||||||
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
|
|
||||||
"S7Documentation.txt");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void OnSelectedItemChanged(ProjectItem? value)
|
public object SelectedObject
|
||||||
{
|
{
|
||||||
SelectedItemDetails = value?.GetDetailsObject();
|
get => _selectedObject;
|
||||||
|
set => SetProperty(ref _selectedObject, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
public object SelectedTreeItem
|
||||||
private async Task LoadProject()
|
{
|
||||||
|
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
|
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()
|
||||||
|
{
|
||||||
|
ProjectStructure = new ObservableCollection<S7Object>();
|
||||||
|
OpenProjectCommand = new RelayCommand(OpenProject);
|
||||||
|
SearchCommand = new RelayCommand(SearchInProject);
|
||||||
|
ProjectInfo = "No hay proyecto abierto";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenProject()
|
||||||
{
|
{
|
||||||
var dialog = new VistaOpenFileDialog
|
var dialog = new VistaOpenFileDialog
|
||||||
{
|
{
|
||||||
Title = "Open Siemens S7 Project",
|
Title = "Seleccionar archivo de proyecto STEP7",
|
||||||
Filter = "S7 Projects (*.s7p)|*.s7p|All files (*.*)|*.*",
|
Filter = "Proyectos STEP7 (*.s7p)|*.s7p|Todos los archivos (*.*)|*.*",
|
||||||
CheckFileExists = true
|
CheckFileExists = true
|
||||||
};
|
};
|
||||||
|
|
||||||
if (dialog.ShowDialog() == true)
|
if (dialog.ShowDialog() == true)
|
||||||
{
|
{
|
||||||
ProjectPath = dialog.FileName;
|
LoadProjectAsync(dialog.FileName);
|
||||||
await LoadProjectFromPath(ProjectPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logService.LogError(ex);
|
|
||||||
MessageBox.Show($"Error loading project: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
private async void LoadProjectAsync(string filePath)
|
||||||
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...";
|
||||||
|
|
||||||
// Load the project
|
// Reiniciar estado
|
||||||
_logService.LogInfo($"Loading project from {path}");
|
ProjectStructure.Clear();
|
||||||
ProjectStructure = await _projectService.LoadProjectAsync(path);
|
_currentProject = null;
|
||||||
|
|
||||||
if (ProjectStructure != null)
|
// Parsear proyecto
|
||||||
{
|
var parser = new S7ProjectParser(filePath);
|
||||||
IsProjectLoaded = true;
|
_currentProject = await parser.ParseProjectAsync();
|
||||||
_logService.LogInfo("Project loaded successfully");
|
|
||||||
}
|
// Actualizar UI
|
||||||
else
|
ProjectStructure.Add(_currentProject);
|
||||||
{
|
_currentProject.IsExpanded = true;
|
||||||
IsProjectLoaded = false;
|
|
||||||
_logService.LogError("Failed to load project");
|
// Actualizar info
|
||||||
MessageBox.Show("Failed to load project", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
ProjectInfo = $"Proyecto: {Path.GetFileNameWithoutExtension(filePath)}";
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logService.LogError(ex);
|
MessageBox.Show($"Error al cargar el proyecto: {ex.Message}", "Error",
|
||||||
MessageBox.Show($"Error loading project: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
ProjectInfo = "Error al cargar el proyecto";
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
@ -143,166 +156,125 @@ namespace S7Explorer.ViewModels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
private void SearchInProject()
|
||||||
private void Search()
|
|
||||||
{
|
{
|
||||||
if (!IsProjectLoaded || ProjectStructure == null)
|
if (_currentProject == null || string.IsNullOrWhiteSpace(SearchText))
|
||||||
{
|
|
||||||
_logService.LogWarning("No project loaded to search");
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(SearchText))
|
|
||||||
{
|
|
||||||
_logService.LogWarning("No search text specified");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var results = _searchHelper.Search(ProjectStructure, SearchText, IsCaseSensitive, UseRegex);
|
// Recopilar todos los objetos en el proyecto
|
||||||
|
var allObjects = GetAllObjects(_currentProject);
|
||||||
|
|
||||||
if (results.Count == 0)
|
// Lista para almacenar coincidencias
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
_logService.LogInfo($"No results found for '{SearchText}'");
|
// Comprobar si el texto de búsqueda parece ser una referencia a FC específica
|
||||||
MessageBox.Show($"No results found for '{SearchText}'", "Search Results", MessageBoxButton.OK, MessageBoxImage.Information);
|
if (searchText.StartsWith("fc") && searchText.Length >= 3)
|
||||||
|
{
|
||||||
|
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show search results dialog
|
// Seleccionar el primer objeto coincidente y expandir su ruta
|
||||||
ShowSearchResults(results);
|
var firstMatch = matchingObjects.First();
|
||||||
}
|
SelectAndExpandToObject(firstMatch);
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logService.LogError(ex);
|
|
||||||
MessageBox.Show($"Error during search: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ShowSearchResults(List<SearchResult> results)
|
// Informar al usuario
|
||||||
|
if (matchingObjects.Count > 1)
|
||||||
{
|
{
|
||||||
// In a full implementation, this would show a dialog or window with search results
|
MessageBox.Show($"Se encontraron {matchingObjects.Count} coincidencias. " +
|
||||||
// For demonstration, we'll just select the first result and expand to it
|
"Mostrando la primera coincidencia.",
|
||||||
if (results.Count > 0)
|
"Búsqueda", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
_logService.LogInfo($"Documentation exported to {ExportSettings.ExportPath}");
|
|
||||||
MessageBox.Show($"Documentation exported to {ExportSettings.ExportPath}", "Export Complete", 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)
|
||||||
{
|
{
|
||||||
_logService.LogError(ex);
|
MessageBox.Show($"Error durante la búsqueda: {ex.Message}",
|
||||||
MessageBox.Show($"Error exporting documentation: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
"Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
IsLoading = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ShowExportSettings()
|
private IEnumerable<S7Object> GetAllObjects(S7Object root)
|
||||||
{
|
{
|
||||||
// Show file save dialog for export path
|
// Devolver el objeto raíz
|
||||||
var dialog = new VistaSaveFileDialog
|
yield return root;
|
||||||
{
|
|
||||||
Title = "Export Documentation",
|
|
||||||
Filter = GetExportFilter(),
|
|
||||||
FileName = Path.GetFileName(ExportSettings.ExportPath),
|
|
||||||
InitialDirectory = Path.GetDirectoryName(ExportSettings.ExportPath)
|
|
||||||
};
|
|
||||||
|
|
||||||
if (dialog.ShowDialog() == true)
|
// Recursivamente devolver todos los hijos
|
||||||
|
foreach (var child in root.Children)
|
||||||
{
|
{
|
||||||
ExportSettings.ExportPath = dialog.FileName;
|
foreach (var obj in GetAllObjects(child))
|
||||||
|
|
||||||
// Determine export format from file extension
|
|
||||||
string extension = Path.GetExtension(dialog.FileName).ToLower();
|
|
||||||
ExportSettings.ExportFormat = extension switch
|
|
||||||
{
|
{
|
||||||
".md" => ExportFormat.MarkDown,
|
yield return obj;
|
||||||
".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");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
private void SelectAndExpandToObject(S7Object obj)
|
||||||
private void CollapseAll()
|
|
||||||
{
|
{
|
||||||
if (ProjectStructure != null)
|
// Expandir todos los nodos padres hasta llegar al objeto
|
||||||
|
var parent = obj.Parent;
|
||||||
|
while (parent != null)
|
||||||
{
|
{
|
||||||
TreeViewHelper.CollapseAll(ProjectStructure);
|
parent.IsExpanded = true;
|
||||||
_logService.LogInfo("Collapsed all tree nodes");
|
parent = parent.Parent;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
// Seleccionar el objeto
|
||||||
private void ClearLog()
|
SelectedTreeItem = obj;
|
||||||
{
|
|
||||||
LogEntries.Clear();
|
|
||||||
_logService.LogInfo("Log cleared");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace S7Explorer.ViewModels
|
||||||
|
{
|
||||||
|
class S7ObjectViewModel
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,15 +0,0 @@
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
|
||||||
using S7Explorer.Services;
|
|
||||||
|
|
||||||
namespace S7Explorer.ViewModels
|
|
||||||
{
|
|
||||||
public class ViewModelBase : ObservableObject
|
|
||||||
{
|
|
||||||
protected readonly LogService _logService;
|
|
||||||
|
|
||||||
public ViewModelBase()
|
|
||||||
{
|
|
||||||
_logService = LogService.Instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
<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>
|
|
@ -0,0 +1,37 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|