7 Commits
11 changed files with 651 additions and 290 deletions
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
+189
View File
@@ -8,6 +8,195 @@
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Localization/Strings.en.xaml"/>
</ResourceDictionary.MergedDictionaries>
<!-- Color Palette -->
<SolidColorBrush x:Key="PrimaryBlue" Color="#004578"/>
<SolidColorBrush x:Key="AccentBlue" Color="#0078D7"/>
<SolidColorBrush x:Key="LightBlue" Color="#E1EEFA"/>
<SolidColorBrush x:Key="GridBlue" Color="#D0E2F2"/>
<SolidColorBrush x:Key="BgWhite" Color="#F5F9FF"/>
<SolidColorBrush x:Key="TextDark" Color="#1B1B1B"/>
<SolidColorBrush x:Key="TextLight" Color="#FFFFFF"/>
<SolidColorBrush x:Key="BorderGray" Color="#CCCCCC"/>
<!-- Modern Button Style -->
<Style x:Key="ModernButtonStyle" TargetType="Button">
<Setter Property="Background" Value="{StaticResource AccentBlue}"/>
<Setter Property="Foreground" Value="{StaticResource TextLight}"/>
<Setter Property="Padding" Value="12,6"/>
<Setter Property="Margin" Value="4"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border x:Name="border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="4">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="border" Property="Background" Value="#005A9E"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="border" Property="Background" Value="#004578"/>
<Setter TargetName="border" Property="RenderTransform">
<Setter.Value>
<ScaleTransform ScaleX="0.98" ScaleY="0.98" CenterX="20" CenterY="10"/>
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Secondary Button Style -->
<Style x:Key="SecondaryButtonStyle" TargetType="Button" BasedOn="{StaticResource ModernButtonStyle}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryBlue}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush" Value="{StaticResource PrimaryBlue}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border x:Name="border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="4">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="border" Property="Background" Value="#D0E2F2"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="border" Property="Background" Value="#B0C2D2"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Header Button Style -->
<Style x:Key="HeaderButtonStyle" TargetType="Button" BasedOn="{StaticResource ModernButtonStyle}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Opacity" Value="0.9"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border x:Name="border"
Background="{TemplateBinding Background}"
CornerRadius="4">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Margin="8,4"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="border" Property="Background" Value="#20FFFFFF"/>
<Setter Property="Opacity" Value="1"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="border" Property="Background" Value="#40FFFFFF"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Modern TextBox Style -->
<Style TargetType="TextBox">
<Setter Property="Padding" Value="8,6"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush" Value="{StaticResource BorderGray}"/>
<Setter Property="Background" Value="White"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TextBox">
<Border x:Name="border"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
CornerRadius="4">
<ScrollViewer x:Name="PART_ContentHost" Focusable="false" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsFocused" Value="True">
<Setter TargetName="border" Property="BorderBrush" Value="{StaticResource AccentBlue}"/>
<Setter TargetName="border" Property="BorderThickness" Value="1.5"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Modern ComboBox Style -->
<Style TargetType="ComboBox">
<Setter Property="Padding" Value="8,4"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush" Value="{StaticResource BorderGray}"/>
<Setter Property="Background" Value="White"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
</Style>
<!-- Modern ListBox Style (for Sidebar) -->
<Style x:Key="SidebarListBoxStyle" TargetType="ListBox">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="ListBoxItem">
<Setter Property="Padding" Value="0"/>
<Setter Property="Margin" Value="4"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border x:Name="border"
Background="{TemplateBinding Background}"
CornerRadius="6"
Padding="4">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="border" Property="Background" Value="#150078D7"/>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="border" Property="Background" Value="#250078D7"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
<!-- Device Container Style -->
<Style x:Key="DeviceContainerStyle" TargetType="Border">
<Setter Property="CornerRadius" Value="8"/>
<Setter Property="Padding" Value="4"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="2"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#100078D7"/>
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>
+1 -3
View File
@@ -1,6 +1,4 @@
using System.Configuration;
using System.Data;
using System.Windows;
using System.Windows;
namespace NetworkDiagram;
Binary file not shown.
+13 -10
View File
@@ -1,10 +1,12 @@
<Window x:Class="NetworkDiagram.EditDeviceWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{DynamicResource EditTitle}" Height="350" Width="400"
Title="{DynamicResource EditTitle}" Height="380" Width="420"
WindowStartupLocation="CenterOwner" ResizeMode="NoResize"
Background="#F5F9FF">
<Grid Margin="20">
Background="{StaticResource BgWhite}"
WindowStyle="ThreeDBorderWindow"
Icon="/Assets/router.jpg">
<Grid Margin="25">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
@@ -13,16 +15,17 @@
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="{DynamicResource DeviceNameLabel}" Margin="0,0,0,5"/>
<TextBox x:Name="NameBox" Grid.Row="1" Margin="0,0,0,15" Padding="5"/>
<TextBlock Text="{DynamicResource DeviceNameLabel}" Margin="0,0,0,8" FontWeight="SemiBold" Foreground="{StaticResource PrimaryBlue}"/>
<TextBox x:Name="NameBox" Grid.Row="1" Margin="0,0,0,20"/>
<TextBlock Grid.Row="2" Text="{DynamicResource IpLabel}" Margin="0,0,0,5"/>
<TextBox x:Name="IpBox" Grid.Row="3" Margin="0,0,0,15" Padding="5"
AcceptsReturn="True" VerticalScrollBarVisibility="Auto"/>
<TextBlock Grid.Row="2" Text="{DynamicResource IpLabel}" Margin="0,0,0,8" FontWeight="SemiBold" Foreground="{StaticResource PrimaryBlue}"/>
<TextBox x:Name="IpBox" Grid.Row="3" Margin="0,0,0,25"
AcceptsReturn="True" VerticalScrollBarVisibility="Auto" MinHeight="80"/>
<StackPanel Grid.Row="4" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content="{DynamicResource CancelBtn}" Click="Cancel_Click" Width="80" Margin="0,0,10,0" Padding="5"/>
<Button Content="{DynamicResource SaveDialogBtn}" Click="Save_Click" Width="80" Padding="5" Background="#0078D7" Foreground="White"/>
<Button Content="{DynamicResource CancelBtn}" Style="{StaticResource SecondaryButtonStyle}" Click="Cancel_Click" Width="90" Margin="0,0,12,0"/>
<Button Content="{DynamicResource SaveDialogBtn}" Style="{StaticResource ModernButtonStyle}" Click="Save_Click" Width="90"/>
</StackPanel>
</Grid>
</Window>
+3 -5
View File
@@ -1,13 +1,11 @@
using System;
using System.Linq;
using System.Windows;
using NetworkDiagram.Models;
namespace NetworkDiagram
{
public partial class EditDeviceWindow : Window
public partial class EditDeviceWindow
{
private PlacedDevice _device;
private readonly PlacedDevice _device;
public EditDeviceWindow(PlacedDevice device)
{
@@ -20,7 +18,7 @@ namespace NetworkDiagram
private void Save_Click(object sender, RoutedEventArgs e)
{
_device.Name = NameBox.Text;
_device.IpAddresses = IpBox.Text.Split(new[] { Environment.NewLine, "\n", "\r" }, StringSplitOptions.RemoveEmptyEntries).ToList();
_device.IpAddresses = IpBox.Text.Split([Environment.NewLine, "\n", "\r"], StringSplitOptions.RemoveEmptyEntries).ToList();
DialogResult = true;
Close();
}
+63 -57
View File
@@ -3,55 +3,45 @@
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:NetworkDiagram"
xmlns:models="clr-namespace:NetworkDiagram.Models"
mc:Ignorable="d"
Title="{DynamicResource AppTitle}" Height="700" Width="1080"
Background="#F5F9FF"
KeyDown="Window_KeyDown">
<Window.Resources>
<SolidColorBrush x:Key="HeaderBlue" Color="#004578"/>
<SolidColorBrush x:Key="AccentBlue" Color="#0078D7"/>
<SolidColorBrush x:Key="SidebarBlue" Color="#E1EEFA"/>
<SolidColorBrush x:Key="CanvasGridBlue" Color="#D0E2F2"/>
<Style x:Key="ModernButton" TargetType="Button">
<Setter Property="Background" Value="{StaticResource AccentBlue}"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Padding" Value="10,5"/>
<Setter Property="Margin" Value="5"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Cursor" Value="Hand"/>
</Style>
</Window.Resources>
Title="{DynamicResource AppTitle}" Height="750" Width="1100"
Background="{StaticResource BgWhite}"
KeyDown="Window_KeyDown"
Icon="/Assets/router.jpg">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="220"/>
<ColumnDefinition Width="240"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="60"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border Grid.Row="0" Grid.ColumnSpan="2" Background="{StaticResource HeaderBlue}" Padding="10">
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Left">
<TextBlock Text="{DynamicResource AppTitle}" Foreground="White" FontSize="18" FontWeight="Bold" VerticalAlignment="Center" Margin="0,0,20,0"/>
<Button Content="{DynamicResource NewBtn}" Style="{StaticResource ModernButton}" Click="NewDiagram_Click"/>
<Button Content="{DynamicResource SaveBtn}" Style="{StaticResource ModernButton}" Click="SaveDiagram_Click"/>
<Button Content="{DynamicResource LoadBtn}" Style="{StaticResource ModernButton}" Click="LoadDiagram_Click"/>
<Button Content="{DynamicResource ExportBtn}" Style="{StaticResource ModernButton}" Click="ExportPng_Click"/>
<Separator Margin="10,0" Background="White" Opacity="0.5"/>
<Button Content="{DynamicResource WireBtn}" Style="{StaticResource ModernButton}" Click="WireTool_Click"/>
<Button Content="{DynamicResource WifiBtn}" Style="{StaticResource ModernButton}" Click="WifiTool_Click"/>
<Button Content="{DynamicResource AddTextBtn}" Style="{StaticResource ModernButton}" Click="AddText_Click"/>
<!-- Header -->
<Border Grid.Row="0" Grid.ColumnSpan="2" Background="{StaticResource PrimaryBlue}" VerticalAlignment="Stretch">
<Border.Effect>
<DropShadowEffect BlurRadius="10" ShadowDepth="2" Opacity="0.3"/>
</Border.Effect>
<DockPanel Margin="15,0">
<StackPanel Orientation="Horizontal" DockPanel.Dock="Left" VerticalAlignment="Center">
<TextBlock Text="{DynamicResource AppTitle}" Foreground="White" FontSize="20" FontWeight="Bold" Margin="0,0,30,0"/>
<Button Content="{DynamicResource NewBtn}" Style="{StaticResource HeaderButtonStyle}" Click="NewDiagram_Click"/>
<Button Content="{DynamicResource SaveBtn}" Style="{StaticResource HeaderButtonStyle}" Click="SaveDiagram_Click"/>
<Button Content="{DynamicResource LoadBtn}" Style="{StaticResource HeaderButtonStyle}" Click="LoadDiagram_Click"/>
<Button Content="{DynamicResource ExportBtn}" Style="{StaticResource HeaderButtonStyle}" Click="ExportPng_Click"/>
<Rectangle Width="1" Height="24" Fill="White" Opacity="0.3" Margin="15,0"/>
<Button Content="{DynamicResource WireBtn}" Style="{StaticResource HeaderButtonStyle}" Click="WireTool_Click"/>
<Button Content="{DynamicResource WifiBtn}" Style="{StaticResource HeaderButtonStyle}" Click="WifiTool_Click"/>
<Button Content="{DynamicResource AddTextBtn}" Style="{StaticResource HeaderButtonStyle}" Click="AddText_Click"/>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<ComboBox x:Name="LangCombo" VerticalAlignment="Center" Width="80" SelectionChanged="LangCombo_SelectionChanged" Margin="10,0,0,0">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center">
<ComboBox x:Name="LangCombo" Width="70" Height="30" SelectionChanged="LangCombo_SelectionChanged">
<ComboBoxItem Tag="en">EN</ComboBoxItem>
<ComboBoxItem Tag="cs" IsSelected="True">CZ</ComboBoxItem>
</ComboBox>
@@ -59,17 +49,21 @@
</DockPanel>
</Border>
<Border Grid.Row="1" Grid.Column="0" Background="{StaticResource SidebarBlue}" BorderBrush="{StaticResource CanvasGridBlue}" BorderThickness="0,0,1,0">
<DockPanel>
<TextBlock Text="{DynamicResource DevicesHeader}" DockPanel.Dock="Top" FontSize="16" FontWeight="SemiBold" Margin="10" Foreground="{StaticResource HeaderBlue}"/>
<ListBox x:Name="ToolboxList" Background="Transparent" BorderThickness="0" Margin="5"
<!-- Sidebar -->
<Border Grid.Row="1" Grid.Column="0" Background="{StaticResource LightBlue}" BorderBrush="{StaticResource GridBlue}" BorderThickness="0,0,1,0">
<DockPanel Margin="10,15">
<TextBlock Text="{DynamicResource DevicesHeader}" DockPanel.Dock="Top" FontSize="18" FontWeight="Bold" Margin="10,0,10,15" Foreground="{StaticResource PrimaryBlue}"/>
<ListBox x:Name="ToolboxList" Style="{StaticResource SidebarListBoxStyle}"
PreviewMouseDown="Toolbox_PreviewMouseDown">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="{StaticResource AccentBlue}" BorderThickness="1" CornerRadius="4" Margin="5" Padding="10" Background="White">
<Border Background="White" CornerRadius="8" Padding="12" BorderBrush="{StaticResource GridBlue}" BorderThickness="1">
<Border.Effect>
<DropShadowEffect BlurRadius="4" ShadowDepth="1" Opacity="0.05"/>
</Border.Effect>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding IconPath}" Width="32" Height="32" Margin="0,0,10,0"/>
<TextBlock Text="{Binding Name}" VerticalAlignment="Center" FontWeight="Medium"/>
<Image Source="{Binding IconPath}" Width="36" Height="36" Margin="0,0,12,0" RenderOptions.BitmapScalingMode="HighQuality"/>
<TextBlock Text="{Binding Name}" VerticalAlignment="Center" FontSize="14" FontWeight="SemiBold" Foreground="{StaticResource TextDark}"/>
</StackPanel>
</Border>
</DataTemplate>
@@ -78,26 +72,38 @@
</DockPanel>
</Border>
<ScrollViewer Grid.Row="1" Grid.Column="1" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Canvas x:Name="DiagramCanvas" Width="2000" Height="2000" AllowDrop="True"
<!-- Canvas Area -->
<Grid Grid.Row="1" Grid.Column="1" x:Name="CanvasViewport" ClipToBounds="True" Background="Transparent"
MouseDown="Viewport_MouseDown"
MouseMove="Viewport_MouseMove"
MouseUp="Viewport_MouseUp"
MouseWheel="Viewport_MouseWheel">
<!-- Increased size to 40k to provide plenty of space in all directions -->
<Canvas x:Name="DiagramCanvas" Width="40000" Height="40000" AllowDrop="True"
Drop="DiagramCanvas_Drop" DragOver="DiagramCanvas_DragOver"
MouseDown="DiagramCanvas_MouseDown"
MouseMove="DiagramCanvas_MouseMove"
MouseUp="DiagramCanvas_MouseUp">
<Canvas.RenderTransform>
<TransformGroup>
<ScaleTransform x:Name="CanvasScale" ScaleX="1" ScaleY="1"/>
<TranslateTransform x:Name="CanvasTranslate" X="-20000" Y="-20000"/>
</TransformGroup>
</Canvas.RenderTransform>
<Canvas.Background>
<VisualBrush TileMode="Tile" Viewport="0,0,40,40" ViewportUnits="Absolute">
<VisualBrush.Visual>
<Path Data="M 0 40 L 0 0 L 40 0" Stroke="{StaticResource CanvasGridBlue}" StrokeThickness="0.5"/>
<Path Data="M 0 40 L 0 0 L 40 0" Stroke="{StaticResource GridBlue}" StrokeThickness="0.8"/>
</VisualBrush.Visual>
</VisualBrush>
</Canvas.Background>
<Canvas.Resources>
<DataTemplate x:Key="DeviceTemplate">
<Border x:Name="DeviceBorder" BorderBrush="Transparent" BorderThickness="2" CornerRadius="4"
Background="Transparent" Padding="5" MaxWidth="150">
<Border x:Name="DeviceBorder" BorderBrush="Transparent" BorderThickness="2" CornerRadius="8"
Background="Transparent" Padding="8" MaxWidth="160">
<StackPanel>
<Image Source="{Binding IconPath}" Width="48" Height="48" HorizontalAlignment="Center">
<Image Source="{Binding IconPath}" Width="56" Height="56" HorizontalAlignment="Center" RenderOptions.BitmapScalingMode="HighQuality">
<Image.Style>
<Style TargetType="Image">
<Style.Triggers>
@@ -108,13 +114,13 @@
</Style>
</Image.Style>
</Image>
<TextBlock Text="{Binding Name}" FontWeight="Bold" HorizontalAlignment="Center"
Foreground="#004578" TextWrapping="Wrap" TextAlignment="Center"/>
<TextBlock Text="{Binding Name}" FontWeight="Bold" FontSize="12" HorizontalAlignment="Center"
Foreground="{StaticResource PrimaryBlue}" TextWrapping="Wrap" TextAlignment="Center" Margin="0,4,0,0"/>
<ItemsControl ItemsSource="{Binding IpAddresses}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" FontSize="10" Foreground="#0078D7"
HorizontalAlignment="Center" TextWrapping="Wrap"/>
<TextBlock Text="{Binding}" FontSize="10" Foreground="{StaticResource AccentBlue}"
HorizontalAlignment="Center" TextWrapping="Wrap" Opacity="0.8"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
@@ -124,9 +130,9 @@
</Canvas.Resources>
<!-- Selection rectangle visual -->
<Rectangle x:Name="SelectionRect" Stroke="{StaticResource AccentBlue}" StrokeDashArray="2,2"
Fill="#330078D7" Visibility="Collapsed" Panel.ZIndex="9999"/>
<Rectangle x:Name="SelectionRect" Stroke="{StaticResource AccentBlue}" StrokeThickness="1.5" StrokeDashArray="3,3"
Fill="#220078D7" Visibility="Collapsed" Panel.ZIndex="9999" RadiusX="2" RadiusY="2" IsHitTestVisible="False"/>
</Canvas>
</ScrollViewer>
</Grid>
</Grid>
</Window>
+327 -178
View File
@@ -1,9 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
@@ -16,13 +12,13 @@ using Path = System.IO.Path;
namespace NetworkDiagram
{
public partial class MainWindow : Window
public partial class MainWindow
{
private List<DeviceTemplate> _deviceTemplates = new List<DeviceTemplate>();
private Diagram _currentDiagram = new Diagram();
private List<DeviceTemplate> _deviceTemplates = [];
private Diagram _currentDiagram = new();
// Selection & Dragging
private HashSet<FrameworkElement> _selectedElements = new HashSet<FrameworkElement>();
private readonly HashSet<FrameworkElement> _selectedElements = [];
private Point _dragStartPoint;
private bool _isDraggingDevices;
private bool _isSelectingArea;
@@ -31,56 +27,142 @@ namespace NetworkDiagram
private ConnectionType? _activeTool;
private PlacedDevice? _firstDeviceForConnection;
private Dictionary<Connection, Line> _connectionLines = new Dictionary<Connection, Line>();
private Dictionary<PlacedDevice, FrameworkElement> _deviceElements = new Dictionary<PlacedDevice, FrameworkElement>();
private readonly Dictionary<Connection, Line> _connectionLines = new();
private readonly Dictionary<PlacedDevice, FrameworkElement> _deviceElements = new();
// Panning and Zooming
private Point _lastPanPoint;
private bool _isPanning;
public MainWindow()
{
InitializeComponent();
LoadTemplates();
this.Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
CenterView();
}
private void CenterView()
{
// Center logically on (20000, 20000) minus half the viewport size to put 0,0 in the middle
CanvasTranslate.X = -20000 + (CanvasViewport.ActualWidth / 2);
CanvasTranslate.Y = -20000 + (CanvasViewport.ActualHeight / 2);
CanvasScale.ScaleX = 1.0;
CanvasScale.ScaleY = 1.0;
}
#region Panning and Zooming
private void Viewport_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Middle)
{
_isPanning = true;
_lastPanPoint = e.GetPosition(CanvasViewport);
CanvasViewport.CaptureMouse();
}
}
private void Viewport_MouseMove(object sender, MouseEventArgs e)
{
if (_isPanning)
{
Point currentPoint = e.GetPosition(CanvasViewport);
double deltaX = currentPoint.X - _lastPanPoint.X;
double deltaY = currentPoint.Y - _lastPanPoint.Y;
CanvasTranslate.X += deltaX;
CanvasTranslate.Y += deltaY;
_lastPanPoint = currentPoint;
}
}
private void Viewport_MouseUp(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Middle)
{
_isPanning = false;
CanvasViewport.ReleaseMouseCapture();
}
}
private void Viewport_MouseWheel(object sender, MouseWheelEventArgs e)
{
double zoom = e.Delta > 0 ? 1.1 : 0.9;
double newScale = CanvasScale.ScaleX * zoom;
if (newScale < 0.1 || newScale > 5) return;
Point mousePos = e.GetPosition(DiagramCanvas);
// Zoom centered on mouse
CanvasScale.ScaleX = newScale;
CanvasScale.ScaleY = newScale;
Point newMousePos = e.GetPosition(DiagramCanvas);
CanvasTranslate.X += (newMousePos.X - mousePos.X) * CanvasScale.ScaleX;
CanvasTranslate.Y += (newMousePos.Y - mousePos.Y) * CanvasScale.ScaleY;
}
#endregion
private void LoadTemplates()
{
try
{
if (File.Exists("devices.json"))
if (!File.Exists("devices.json")) return;
var json = File.ReadAllText("devices.json");
_deviceTemplates = JsonSerializer.Deserialize<List<DeviceTemplate>>(json) ?? [];
foreach (var t in _deviceTemplates.Where(t => !string.IsNullOrEmpty(t.IconPath)))
{
string json = File.ReadAllText("devices.json");
_deviceTemplates = JsonSerializer.Deserialize<List<DeviceTemplate>>(json) ?? new List<DeviceTemplate>();
foreach(var t in _deviceTemplates)
{
if (!string.IsNullOrEmpty(t.IconPath)) t.IconPath = Path.GetFullPath(t.IconPath);
}
ToolboxList.ItemsSource = _deviceTemplates;
t.IconPath = Path.GetFullPath(t.IconPath);
}
ToolboxList.ItemsSource = _deviceTemplates;
}
catch (Exception ex) { MessageBox.Show($"Error loading templates: {ex.Message}"); }
}
private string GetLocalizedString(string key) => Application.Current.Resources[key] as string ?? key;
private static string GetLocalizedString(string key) => Application.Current.Resources[key] as string ?? key;
private void LangCombo_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (LangCombo.SelectedItem is ComboBoxItem item && item.Tag is string lang)
{
var dict = new ResourceDictionary();
dict.Source = new Uri($"Localization/Strings.{lang}.xaml", UriKind.Relative);
if (LangCombo.SelectedItem is not ComboBoxItem { Tag: string lang }) return;
// Replace the existing localization dictionary
var oldDict = Application.Current.Resources.MergedDictionaries.FirstOrDefault(d => d.Source != null && d.Source.OriginalString.Contains("Localization/Strings."));
if (oldDict != null) Application.Current.Resources.MergedDictionaries.Remove(oldDict);
Application.Current.Resources.MergedDictionaries.Add(dict);
}
var dict = new ResourceDictionary
{
Source = new Uri($"Localization/Strings.{lang}.xaml", UriKind.Relative)
};
// Replace the existing localization dictionary
var oldDict = Application.Current.Resources.MergedDictionaries.FirstOrDefault(d => d.Source != null && d.Source.OriginalString.Contains("Localization/Strings."));
if (oldDict != null) Application.Current.Resources.MergedDictionaries.Remove(oldDict);
Application.Current.Resources.MergedDictionaries.Add(dict);
}
#region Drag and Drop (Toolbox to Canvas)
private void Toolbox_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
if (sender is ListBox listBox && listBox.SelectedItem is DeviceTemplate template)
DragDrop.DoDragDrop(listBox, template, DragDropEffects.Copy);
if (sender is ListBox listBox)
{
// Find the item under the mouse instead of relying on SelectedItem
var element = e.OriginalSource as DependencyObject;
while (element != null && !(element is ListBoxItem))
element = VisualTreeHelper.GetParent(element);
if (element is ListBoxItem item && item.Content is DeviceTemplate template)
{
DragDrop.DoDragDrop(listBox, template, DragDropEffects.Copy);
}
}
}
private void DiagramCanvas_DragOver(object sender, DragEventArgs e)
{
e.Effects = e.Data.GetDataPresent(typeof(DeviceTemplate)) ? DragDropEffects.Copy : DragDropEffects.None;
@@ -89,18 +171,17 @@ namespace NetworkDiagram
private void DiagramCanvas_Drop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(typeof(DeviceTemplate)))
{
var template = (DeviceTemplate)e.Data.GetData(typeof(DeviceTemplate));
Point dropPoint = e.GetPosition(DiagramCanvas);
AddDeviceToCanvas(template, dropPoint.X, dropPoint.Y);
}
if (!e.Data.GetDataPresent(typeof(DeviceTemplate))) return;
var template = (DeviceTemplate)e.Data.GetData(typeof(DeviceTemplate));
var dropPoint = e.GetPosition(DiagramCanvas);
AddDeviceToCanvas(template, dropPoint.X, dropPoint.Y);
}
#endregion
private void AddDeviceToCanvas(DeviceTemplate template, double x, double y)
{
var placed = new PlacedDevice { Name = template.Name, IconPath = template.IconPath, X = x, Y = y };
var placed = new PlacedDevice { Name = template.Name, TemplateName = template.Name, IconPath = template.IconPath, X = x, Y = y };
_currentDiagram.Devices.Add(placed);
RenderDevice(placed);
}
@@ -109,23 +190,35 @@ namespace NetworkDiagram
{
var template = (DataTemplate)DiagramCanvas.Resources["DeviceTemplate"];
var contentPresenter = new ContentPresenter { Content = device, ContentTemplate = template };
var container = new Border { Child = contentPresenter, Tag = device, Background = Brushes.Transparent };
var container = new Border {
Child = contentPresenter,
Tag = device,
Style = (Style)Application.Current.Resources["DeviceContainerStyle"]
};
Canvas.SetLeft(container, device.X);
Canvas.SetTop(container, device.Y);
device.PropertyChanged += (s, e) => {
if (e.PropertyName == nameof(PlacedDevice.X)) Canvas.SetLeft(container, device.X);
if (e.PropertyName == nameof(PlacedDevice.Y)) Canvas.SetTop(container, device.Y);
device.PropertyChanged += (s, e) =>
{
switch (e.PropertyName)
{
case nameof(PlacedDevice.X):
Canvas.SetLeft(container, device.X);
break;
case nameof(PlacedDevice.Y):
Canvas.SetTop(container, device.Y);
break;
}
};
container.MouseDown += Device_MouseDown;
container.MouseLeftButtonDown += (s, e) => {
if (e.ClickCount == 2) {
StopDragging();
EditDevice(device);
e.Handled = true;
}
container.MouseLeftButtonDown += (s, e) =>
{
if (e.ClickCount != 2) return;
StopDragging();
EditDevice(device);
e.Handled = true;
};
_deviceElements[device] = container;
@@ -142,50 +235,66 @@ namespace NetworkDiagram
private void Device_MouseDown(object sender, MouseButtonEventArgs e)
{
if (sender is FrameworkElement element && element.Tag is PlacedDevice device)
if (sender is not FrameworkElement { Tag: PlacedDevice device } element) return;
if (_activeTool != null)
{
if (_activeTool != null)
{
HandleConnectionTool(device, element);
e.Handled = true;
return;
}
// Handle Selection
if (!Keyboard.IsKeyDown(Key.LeftCtrl) && !Keyboard.IsKeyDown(Key.RightCtrl))
{
if (!_selectedElements.Contains(element))
{
ClearSelection();
SelectElement(element);
}
}
else
{
if (_selectedElements.Contains(element)) DeselectElement(element);
else SelectElement(element);
}
// Start Dragging
_isDraggingDevices = true;
_dragStartPoint = e.GetPosition(DiagramCanvas);
DiagramCanvas.CaptureMouse();
HandleConnectionTool(device, element);
e.Handled = true;
return;
}
// Handle Selection
if (!Keyboard.IsKeyDown(Key.LeftCtrl) && !Keyboard.IsKeyDown(Key.RightCtrl))
{
if (!_selectedElements.Contains(element))
{
ClearSelection();
SelectElement(element);
}
}
else
{
if (_selectedElements.Contains(element)) DeselectElement(element);
else SelectElement(element);
}
// Start Dragging
_isDraggingDevices = true;
_dragStartPoint = e.GetPosition(DiagramCanvas);
DiagramCanvas.CaptureMouse();
e.Handled = true;
}
private void SelectElement(FrameworkElement element)
{
_selectedElements.Add(element);
element.Opacity = 0.7;
if (element is Line l) l.StrokeThickness = 5;
if (element is Border b)
{
b.BorderBrush = (SolidColorBrush)Application.Current.Resources["AccentBlue"];
b.Background = new SolidColorBrush(Color.FromArgb(30, 0, 120, 215)); // Light blue tint
}
else if (element is Line l)
{
l.StrokeThickness = 5;
l.Opacity = 0.8;
}
}
private void DeselectElement(FrameworkElement element)
{
_selectedElements.Remove(element);
element.Opacity = 1.0;
if (element is Line l) l.StrokeThickness = 3;
switch (element)
{
case Border b:
b.BorderBrush = Brushes.Transparent;
b.Background = Brushes.Transparent;
break;
case Line l:
l.StrokeThickness = 3;
l.Opacity = 1.0;
break;
}
}
private void ClearSelection()
@@ -213,14 +322,14 @@ namespace NetworkDiagram
private void DiagramCanvas_MouseMove(object sender, MouseEventArgs e)
{
Point currentPoint = e.GetPosition(DiagramCanvas);
var currentPoint = e.GetPosition(DiagramCanvas);
if (_isSelectingArea)
{
double x = Math.Min(_selectionStartPoint.X, currentPoint.X);
double y = Math.Min(_selectionStartPoint.Y, currentPoint.Y);
double w = Math.Abs(_selectionStartPoint.X - currentPoint.X);
double h = Math.Abs(_selectionStartPoint.Y - currentPoint.Y);
var x = Math.Min(_selectionStartPoint.X, currentPoint.X);
var y = Math.Min(_selectionStartPoint.Y, currentPoint.Y);
var w = Math.Abs(_selectionStartPoint.X - currentPoint.X);
var h = Math.Abs(_selectionStartPoint.Y - currentPoint.Y);
Canvas.SetLeft(SelectionRect, x);
Canvas.SetTop(SelectionRect, y);
@@ -228,10 +337,10 @@ namespace NetworkDiagram
SelectionRect.Height = h;
// Real-time selection preview
Rect selectionBounds = new Rect(x, y, w, h);
var selectionBounds = new Rect(x, y, w, h);
foreach (var border in _deviceElements.Values)
{
Rect elementBounds = new Rect(Canvas.GetLeft(border), Canvas.GetTop(border), border.ActualWidth, border.ActualHeight);
var elementBounds = new Rect(Canvas.GetLeft(border), Canvas.GetTop(border), border.ActualWidth, border.ActualHeight);
if (selectionBounds.IntersectsWith(elementBounds))
{
if (!_selectedElements.Contains(border)) SelectElement(border);
@@ -246,7 +355,7 @@ namespace NetworkDiagram
// Also preview for lines
foreach (var line in _connectionLines.Values)
{
Rect lineBounds = new Rect(
var lineBounds = new Rect(
Math.Min(line.X1, line.X2),
Math.Min(line.Y1, line.Y2),
Math.Abs(line.X1 - line.X2),
@@ -264,8 +373,8 @@ namespace NetworkDiagram
}
else if (_isDraggingDevices && e.LeftButton == MouseButtonState.Pressed)
{
double deltaX = currentPoint.X - _dragStartPoint.X;
double deltaY = currentPoint.Y - _dragStartPoint.Y;
var deltaX = currentPoint.X - _dragStartPoint.X;
var deltaY = currentPoint.Y - _dragStartPoint.Y;
foreach (var element in _selectedElements)
{
@@ -283,7 +392,7 @@ namespace NetworkDiagram
{
if (_isSelectingArea)
{
Rect selectionBounds = new Rect(
var selectionBounds = new Rect(
Canvas.GetLeft(SelectionRect),
Canvas.GetTop(SelectionRect),
SelectionRect.Width,
@@ -291,7 +400,7 @@ namespace NetworkDiagram
foreach (var border in _deviceElements.Values)
{
Rect elementBounds = new Rect(Canvas.GetLeft(border), Canvas.GetTop(border), border.ActualWidth, border.ActualHeight);
var elementBounds = new Rect(Canvas.GetLeft(border), Canvas.GetTop(border), border.ActualWidth, border.ActualHeight);
if (selectionBounds.IntersectsWith(elementBounds)) SelectElement(border);
}
}
@@ -333,7 +442,7 @@ namespace NetworkDiagram
StrokeThickness = 3,
Tag = conn
};
if (conn.Type == ConnectionType.Wifi) line.StrokeDashArray = new DoubleCollection { 2, 2 };
if (conn.Type == ConnectionType.Wifi) line.StrokeDashArray = [2, 2];
UpdateLinePosition(conn, line);
@@ -352,60 +461,68 @@ namespace NetworkDiagram
private void UpdateLinePosition(Connection conn, Line line)
{
if (_deviceElements.TryGetValue(conn.StartDevice, out var startElem) &&
_deviceElements.TryGetValue(conn.EndDevice, out var endElem))
{
if (startElem.ActualWidth == 0) startElem.UpdateLayout();
if (endElem.ActualWidth == 0) endElem.UpdateLayout();
if (!_deviceElements.TryGetValue(conn.StartDevice, out var startElem) ||
!_deviceElements.TryGetValue(conn.EndDevice, out var endElem)) return;
line.X1 = conn.StartDevice.X + (startElem.ActualWidth / 2);
line.Y1 = conn.StartDevice.Y + 24;
line.X2 = conn.EndDevice.X + (endElem.ActualWidth / 2);
line.Y2 = conn.EndDevice.Y + 24;
}
if (startElem.ActualWidth == 0) startElem.UpdateLayout();
if (endElem.ActualWidth == 0) endElem.UpdateLayout();
line.X1 = conn.StartDevice.X + (startElem.ActualWidth / 2);
line.Y1 = conn.StartDevice.Y + 24;
line.X2 = conn.EndDevice.X + (endElem.ActualWidth / 2);
line.Y2 = conn.EndDevice.Y + 24;
}
private void Window_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Delete && _selectedElements.Count > 0)
switch (e.Key)
{
foreach (var el in _selectedElements.ToList())
case Key.Delete when _selectedElements.Count > 0:
{
if (el is Line line && el.Tag is Connection conn)
foreach (var el in _selectedElements.ToList())
{
_currentDiagram.Connections.Remove(conn);
_connectionLines.Remove(conn);
DiagramCanvas.Children.Remove(line);
}
else if (el is Border border && border.Tag is PlacedDevice device)
{
_currentDiagram.Devices.Remove(device);
_deviceElements.Remove(device);
var toRemove = _currentDiagram.Connections.Where(c => c.StartDevice == device || c.EndDevice == device).ToList();
foreach (var c in toRemove)
switch (el)
{
if (_connectionLines.TryGetValue(c, out var l)) DiagramCanvas.Children.Remove(l);
_currentDiagram.Connections.Remove(c);
_connectionLines.Remove(c);
case Line line when el.Tag is Connection conn:
_currentDiagram.Connections.Remove(conn);
_connectionLines.Remove(conn);
DiagramCanvas.Children.Remove(line);
break;
case Border { Tag: PlacedDevice device } border:
{
_currentDiagram.Devices.Remove(device);
_deviceElements.Remove(device);
var toRemove = _currentDiagram.Connections.Where(c => c.StartDevice == device || c.EndDevice == device).ToList();
foreach (var c in toRemove)
{
if (_connectionLines.TryGetValue(c, out var l)) DiagramCanvas.Children.Remove(l);
_currentDiagram.Connections.Remove(c);
_connectionLines.Remove(c);
}
DiagramCanvas.Children.Remove(border);
break;
}
}
DiagramCanvas.Children.Remove(border);
}
_selectedElements.Clear();
break;
}
_selectedElements.Clear();
case Key.Escape:
ResetConnectionTool(); StopDragging(); ClearSelection();
break;
}
else if (e.Key == Key.Escape) { ResetConnectionTool(); StopDragging(); ClearSelection(); }
}
private void EditDevice(PlacedDevice device)
{
var dialog = new EditDeviceWindow(device);
dialog.ShowDialog();
if (_deviceElements.TryGetValue(device, out var elem))
{
elem.UpdateLayout();
var attached = _currentDiagram.Connections.Where(c => c.StartDevice == device || c.EndDevice == device).ToList();
foreach(var c in attached) if (_connectionLines.TryGetValue(c, out var l)) UpdateLinePosition(c, l);
}
if (!_deviceElements.TryGetValue(device, out var elem)) return;
elem.UpdateLayout();
var attached = _currentDiagram.Connections.Where(c => c.StartDevice == device || c.EndDevice == device).ToList();
foreach(var c in attached) if (_connectionLines.TryGetValue(c, out var l)) UpdateLinePosition(c, l);
}
#region Toolbar Events
@@ -413,49 +530,78 @@ namespace NetworkDiagram
{
_currentDiagram = new Diagram();
DiagramCanvas.Children.Clear();
// Re-add selection rectangle after clearing
if (SelectionRect != null) DiagramCanvas.Children.Add(SelectionRect);
_connectionLines.Clear();
_deviceElements.Clear();
ClearSelection();
CenterView();
}
private void SaveDiagram_Click(object sender, RoutedEventArgs e)
{
var sfd = new SaveFileDialog { Filter = "Network Diagram (*.ndjson)|*.ndjson" };
if (sfd.ShowDialog() == true)
{
var model = new DiagramSaveModel {
Devices = _currentDiagram.Devices,
Connections = _currentDiagram.Connections.Select(c => new ConnectionSaveModel {
StartIndex = _currentDiagram.Devices.IndexOf(c.StartDevice),
EndIndex = _currentDiagram.Devices.IndexOf(c.EndDevice),
Type = c.Type
}).ToList()
};
string json = JsonSerializer.Serialize(model, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(sfd.FileName, json);
}
if (sfd.ShowDialog() != true) return;
var model = new DiagramSaveModel {
Devices = _currentDiagram.Devices,
Connections = _currentDiagram.Connections.Select(c => new ConnectionSaveModel {
StartIndex = _currentDiagram.Devices.IndexOf(c.StartDevice),
EndIndex = _currentDiagram.Devices.IndexOf(c.EndDevice),
Type = c.Type
}).ToList()
};
var json = JsonSerializer.Serialize(model, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(sfd.FileName, json);
}
private void LoadDiagram_Click(object sender, RoutedEventArgs e)
{
var ofd = new OpenFileDialog { Filter = "Network Diagram (*.ndjson)|*.ndjson" };
if (ofd.ShowDialog() == true)
if (ofd.ShowDialog() != true) return;
var json = File.ReadAllText(ofd.FileName);
var model = JsonSerializer.Deserialize<DiagramSaveModel>(json);
if (model == null) return;
_currentDiagram = new Diagram { Devices = model.Devices };
foreach (var device in _currentDiagram.Devices)
{
string json = File.ReadAllText(ofd.FileName);
var model = JsonSerializer.Deserialize<DiagramSaveModel>(json);
if (model == null) return;
_currentDiagram = new Diagram { Devices = model.Devices };
DiagramCanvas.Children.Clear();
_connectionLines.Clear();
_deviceElements.Clear();
ClearSelection();
foreach (var device in _currentDiagram.Devices) RenderDevice(device);
foreach (var cModel in model.Connections)
{
var conn = new Connection { StartDevice = _currentDiagram.Devices[cModel.StartIndex], EndDevice = _currentDiagram.Devices[cModel.EndIndex], Type = cModel.Type };
_currentDiagram.Connections.Add(conn);
RenderConnection(conn);
}
var template = _deviceTemplates.FirstOrDefault(t => t.Name == device.TemplateName);
if (template != null) device.IconPath = template.IconPath;
}
DiagramCanvas.Children.Clear();
// Re-add selection rectangle after clearing
if (SelectionRect != null) DiagramCanvas.Children.Add(SelectionRect);
_connectionLines.Clear();
_deviceElements.Clear();
ClearSelection();
foreach (var device in _currentDiagram.Devices) RenderDevice(device);
foreach (var conn in model.Connections.Select(cModel => new Connection { StartDevice = _currentDiagram.Devices[cModel.StartIndex], EndDevice = _currentDiagram.Devices[cModel.EndIndex], Type = cModel.Type }))
{
_currentDiagram.Connections.Add(conn);
RenderConnection(conn);
}
// Center on loaded content
if (_currentDiagram.Devices.Count > 0)
{
double minX = _currentDiagram.Devices.Min(d => d.X);
double minY = _currentDiagram.Devices.Min(d => d.Y);
double maxX = _currentDiagram.Devices.Max(d => d.X);
double maxY = _currentDiagram.Devices.Max(d => d.Y);
CanvasTranslate.X = -((minX + maxX) / 2) * CanvasScale.ScaleX + (CanvasViewport.ActualWidth / 2);
CanvasTranslate.Y = -((minY + maxY) / 2) * CanvasScale.ScaleY + (CanvasViewport.ActualHeight / 2);
}
else
{
CenterView();
}
}
@@ -477,14 +623,14 @@ namespace NetworkDiagram
var result = MessageBox.Show(GetLocalizedString("ExportWhiteBgMsg"), GetLocalizedString("ExportOptionsTitle"), MessageBoxButton.YesNoCancel);
if (result == MessageBoxResult.Cancel) return;
bool whiteBg = result == MessageBoxResult.Yes;
var whiteBg = result == MessageBoxResult.Yes;
// 1. Find content bounds
double minX = double.MaxValue, minY = double.MaxValue, maxX = double.MinValue, maxY = double.MinValue;
foreach (var border in _deviceElements.Values)
{
double x = Canvas.GetLeft(border);
double y = Canvas.GetTop(border);
var x = Canvas.GetLeft(border);
var y = Canvas.GetTop(border);
minX = Math.Min(minX, x);
minY = Math.Min(minY, y);
maxX = Math.Max(maxX, x + border.ActualWidth);
@@ -498,16 +644,16 @@ namespace NetworkDiagram
maxY = Math.Max(maxY, Math.Max(line.Y1, line.Y2));
}
double margin = 20;
const double margin = 20;
minX -= margin; minY -= margin; maxX += margin; maxY += margin;
double width = Math.Max(1, maxX - minX);
double height = Math.Max(1, maxY - minY);
var width = Math.Max(1, maxX - minX);
var height = Math.Max(1, maxY - minY);
try
{
RenderTargetBitmap rtb = new RenderTargetBitmap((int)width, (int)height, 96, 96, PixelFormats.Pbgra32);
DrawingVisual dv = new DrawingVisual();
using (DrawingContext dc = dv.RenderOpen())
var rtb = new RenderTargetBitmap((int)width, (int)height, 96, 96, PixelFormats.Pbgra32);
var dv = new DrawingVisual();
using (var dc = dv.RenderOpen())
{
if (whiteBg) dc.DrawRectangle(Brushes.White, null, new Rect(0, 0, width, height));
dc.PushTransform(new TranslateTransform(-minX, -minY));
@@ -516,23 +662,26 @@ namespace NetworkDiagram
{
if (child == SelectionRect || !(child is Visual v) || ((UIElement)child).Visibility != Visibility.Visible) continue;
double left = Canvas.GetLeft((UIElement)child);
double top = Canvas.GetTop((UIElement)child);
var left = Canvas.GetLeft((UIElement)child);
var top = Canvas.GetTop((UIElement)child);
if (child is Line line)
switch (child)
{
dc.DrawLine(new Pen(line.Stroke, line.StrokeThickness) { DashStyle = new DashStyle(line.StrokeDashArray, 0) },
new Point(line.X1, line.Y1), new Point(line.X2, line.Y2));
}
else if (child is FrameworkElement fe)
{
VisualBrush vb = new VisualBrush(fe) { Stretch = Stretch.None };
dc.DrawRectangle(vb, null, new Rect(left, top, fe.ActualWidth, fe.ActualHeight));
case Line line:
dc.DrawLine(new Pen(line.Stroke, line.StrokeThickness) { DashStyle = new DashStyle(line.StrokeDashArray, 0) },
new Point(line.X1, line.Y1), new Point(line.X2, line.Y2));
break;
case FrameworkElement fe:
{
var vb = new VisualBrush(fe) { Stretch = Stretch.None };
dc.DrawRectangle(vb, null, new Rect(left, top, fe.ActualWidth, fe.ActualHeight));
break;
}
}
}
}
rtb.Render(dv);
PngBitmapEncoder encoder = new PngBitmapEncoder();
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(rtb));
using (var stream = File.Create(sfd.FileName)) encoder.Save(stream);
MessageBox.Show(GetLocalizedString("ExportSuccess"));
@@ -543,11 +692,11 @@ namespace NetworkDiagram
}
public class DiagramSaveModel {
public List<PlacedDevice> Devices { get; set; } = new();
public List<ConnectionSaveModel> Connections { get; set; } = new();
public List<PlacedDevice> Devices { get; set; } = [];
public List<ConnectionSaveModel> Connections { get; set; } = [];
}
public class ConnectionSaveModel {
public int StartIndex { get; set; }
public int StartIndex { get; init; }
public int EndIndex { get; set; }
public ConnectionType Type { get; set; }
}
+37 -27
View File
@@ -1,6 +1,6 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text.Json.Serialization;
namespace NetworkDiagram.Models
{
@@ -12,55 +12,65 @@ namespace NetworkDiagram.Models
public class DeviceTemplate
{
public string Name { get; set; } = string.Empty;
public string Name { get; init; } = string.Empty;
public string IconPath { get; set; } = string.Empty;
}
public class PlacedDevice : INotifyPropertyChanged
{
private string _name = string.Empty;
private List<string> _ipAddresses = new List<string>();
private string _annotation = string.Empty;
private double _x;
private double _y;
public string Name
{
get => _name;
set { _name = value; OnPropertyChanged(); }
}
get;
set
{
field = value;
OnPropertyChanged();
}
} = string.Empty;
public string TemplateName { get; set; } = string.Empty;
[JsonIgnore]
public string IconPath { get; set; } = string.Empty;
public double X
{
get => _x;
set { _x = value; OnPropertyChanged(); OnPropertyChanged(nameof(CenterX)); }
get;
set
{
field = value;
OnPropertyChanged();
OnPropertyChanged(nameof(CenterX));
}
}
public double Y
{
get => _y;
set { _y = value; OnPropertyChanged(); OnPropertyChanged(nameof(CenterY)); }
get;
set
{
field = value;
OnPropertyChanged();
OnPropertyChanged(nameof(CenterY));
}
}
public List<string> IpAddresses
{
get => _ipAddresses;
set { _ipAddresses = value; OnPropertyChanged(); }
}
public string Annotation
{
get => _annotation;
set { _annotation = value; OnPropertyChanged(); }
}
get;
set
{
field = value;
OnPropertyChanged();
}
} = [];
public double CenterX => X + 50;
public double CenterY => Y + 40;
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string? name = null)
private void OnPropertyChanged([CallerMemberName] string? name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
@@ -75,7 +85,7 @@ namespace NetworkDiagram.Models
public class Diagram
{
public List<PlacedDevice> Devices { get; set; } = new List<PlacedDevice>();
public List<Connection> Connections { get; set; } = new List<Connection>();
public List<PlacedDevice> Devices { get; set; } = [];
public List<Connection> Connections { get; set; } = [];
}
}
+1
View File
@@ -12,6 +12,7 @@
<None Update="devices.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<Resource Include="Assets\**" />
<None Update="Assets\**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
+4 -4
View File
@@ -8,12 +8,12 @@
"IconPath": "Assets/workgroup switch.jpg"
},
{
"Name": "Server",
"IconPath": "Assets/fileserver.jpg"
"Name": "L3 Switch",
"IconPath": "Assets/layer 3 switch.jpg"
},
{
"Name": "Storage server",
"IconPath": "Assets/storage server.jpg"
"Name": "Server",
"IconPath": "Assets/fileserver.jpg"
},
{
"Name": "Personal Computer",