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