From 1badf9db586da6fa7045745a2b7a667b295eef64 Mon Sep 17 00:00:00 2001 From: Giuliano Paschoalino Date: Mon, 14 Jul 2025 11:44:57 -0300 Subject: [PATCH] =?UTF-8?q?Import=20inicial:=20migra=C3=A7=C3=A3o=20de=20a?= =?UTF-8?q?rquivos=20da=20rede?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +- App.xaml | 2 +- App.xaml.cs | 2 - BackgroundBuilder.csproj | 65 ++++++---- Properties/Settings.Designer.cs | 26 ++++ Properties/Settings.settings | 6 + README.md | 87 ++++++++----- Services/ContactService.cs | 36 ++++++ Services/DatabaseService.cs | 5 +- Services/IContactService.cs | 18 +++ Services/IImageService.cs | 2 +- Services/IMessageService.cs | 10 ++ Services/ImageService.cs | 120 ++++++++++++----- Services/MessageService.cs | 13 ++ Thumbs.db | Bin 0 -> 5120 bytes ViewModels/MainWindowViewModel.cs | 24 +++- Views/MainWindow.xaml | 205 ++++++++++++++++-------------- Views/MainWindow.xaml.cs | 8 ++ appsettings.json | 5 - resume.md | 149 +++++++++++----------- smart.ico | Bin 0 -> 51262 bytes 21 files changed, 513 insertions(+), 273 deletions(-) create mode 100644 Properties/Settings.Designer.cs create mode 100644 Properties/Settings.settings create mode 100644 Services/ContactService.cs create mode 100644 Services/IContactService.cs create mode 100644 Services/IMessageService.cs create mode 100644 Services/MessageService.cs create mode 100644 Thumbs.db delete mode 100644 appsettings.json create mode 100644 smart.ico diff --git a/.gitignore b/.gitignore index 9491a2f..1753ed8 100644 --- a/.gitignore +++ b/.gitignore @@ -360,4 +360,5 @@ MigrationBackup/ .ionide/ # Fody - auto-generated XML schema -FodyWeavers.xsd \ No newline at end of file +FodyWeavers.xsd +.history/ \ No newline at end of file diff --git a/App.xaml b/App.xaml index 919c746..25fa19c 100644 --- a/App.xaml +++ b/App.xaml @@ -14,7 +14,7 @@ - + diff --git a/App.xaml.cs b/App.xaml.cs index ed4a219..f85cdb4 100644 --- a/App.xaml.cs +++ b/App.xaml.cs @@ -17,8 +17,6 @@ namespace BackgroundBuilder public App() { _host = Host.CreateDefaultBuilder() - .ConfigureAppConfiguration(cfg => - cfg.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)) .ConfigureServices((_, services) => { // Database & repository diff --git a/BackgroundBuilder.csproj b/BackgroundBuilder.csproj index f70ee4d..a438ff2 100644 --- a/BackgroundBuilder.csproj +++ b/BackgroundBuilder.csproj @@ -1,29 +1,44 @@  - - WinExe - net9.0-windows7.0 - true - BackgroundBuilder - BackgroundBuilder - enable - true - - - - - - - - - - - - - - - Always - - + + WinExe + net9.0-windows7.0 + true + BackgroundBuilder + BackgroundBuilder + enable + true + smart.ico + smart.ico + + + + + + + + + + + + + + + + True + True + Settings.settings + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + \ + + diff --git a/Properties/Settings.Designer.cs b/Properties/Settings.Designer.cs new file mode 100644 index 0000000..700b5d6 --- /dev/null +++ b/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// O código foi gerado por uma ferramenta. +// Versão de Tempo de Execução:4.0.30319.42000 +// +// As alterações ao arquivo poderão causar comportamento incorreto e serão perdidas se +// o código for gerado novamente. +// +//------------------------------------------------------------------------------ + +namespace BackgroundBuilder.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.12.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/Properties/Settings.settings b/Properties/Settings.settings new file mode 100644 index 0000000..049245f --- /dev/null +++ b/Properties/Settings.settings @@ -0,0 +1,6 @@ + + + + + + diff --git a/README.md b/README.md index b3c668e..99167f1 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,65 @@ # BackgroundBuilder -An MVVM WPF application (.NET 6) providing an Excel-like editor for the `contatos` table in PostgreSQL. +[![.NET](https://img.shields.io/badge/.NET-8.0-blueviolet?logo=dotnet&logoColor=white)](https://dotnet.microsoft.com/) [![WPF](https://img.shields.io/badge/WPF-%23C8C8C8?logo=windows&logoColor=blue)](https://learn.microsoft.com/dotnet/desktop/wpf/) [![XAML](https://img.shields.io/badge/XAML-0C54C2?logo=xaml&logoColor=white)](https://learn.microsoft.com/dotnet/desktop/wpf/xaml/) [![C#](https://img.shields.io/badge/C%23-239120?logo=c-sharp&logoColor=white)](https://learn.microsoft.com/dotnet/csharp/) [![MVVM](https://img.shields.io/badge/Pattern-MVVM-ff69b4)](https://learn.microsoft.com/pt-br/dotnet/architecture/maui/mvvm) [![DI](https://img.shields.io/badge/DI-Microsoft.Extensions.Hosting-0078D7?logo=azure-devops&logoColor=white)](https://learn.microsoft.com/dotnet/core/extensions/dependency-injection) [![PostgreSQL](https://img.shields.io/badge/DB-PostgreSQL-4169E1?logo=postgresql&logoColor=white)](https://www.postgresql.org/) [![Npgsql](https://img.shields.io/badge/Driver-Npgsql-008bb9?logo=postgresql&logoColor=white)](https://www.npgsql.org/) [![Dapper](https://img.shields.io/badge/ORM-Dapper-0089D6)](https://github.com/DapperLib/Dapper) [![Windows](https://img.shields.io/badge/Platform-Windows-0078D6?logo=windows&logoColor=white)](https://www.microsoft.com/windows) -## Prerequisites +## 📝 Project description -- .NET 6 SDK -- PostgreSQL database with table: +> 🖥️ An MVVM WPF application (.NET 8) providing an Excel-like editor for the `contatos` table in PostgreSQL - ```sql - CREATE TABLE public.contatos ( - ramal text PRIMARY KEY NOT NULL, - nome text NOT NULL, - email text, - area text, - aniversario date, - "isComando" boolean NOT NULL - ); - ``` -## Setup -1. Edit appsettings.json, set your ConnectionStrings:ContatosDb. -2. In a terminal: - ```bash - dotnet restore - dotnet build - dotnet run --project BackgroundBuilder.csproj - ``` -3. The main window will appear; on load it fetches and displays all contatos. - -## Architecture -- MVVM with MVVM with ObservableObject, RelayCommand. -- DI via Microsoft.Extensions.Hosting. -- Repositories (PostgresContatoRepository) handle all DB I/O with Dapper. -- Services (DatabaseService) manage the Npgsql connection. -- ViewModels free of data-access logic: only orchestration. --- -### New Background & Export Features +## 📑 Prerequisites -- **Select Background…** +- [![.NET](https://img.shields.io/badge/.NET_8.0-blueviolet?logo=dotnet&logoColor=white)](https://dotnet.microsoft.com/) +- [![PostgreSQL](https://img.shields.io/badge/PostgreSQL-4169E1?logo=postgresql&logoColor=white)](https://www.postgresql.org/) **database** with table: + +```sql + CREATE TABLE public.contatos ( + ramal text PRIMARY KEY NOT NULL, + nome text NOT NULL, + email text, + area text, + aniversario date, + "isComando" boolean NOT NULL + ); +``` + +--- + +## 🔧 Setup + +1. ✏️ Edit `appsettings.json`, set your `ConnectionStrings:ContatosDb`. +2. 🖥️ In a terminal: + + ```bash + dotnet restore + dotnet build + dotnet run --project BackgroundBuilder.csproj + ``` + +3. 🪟 The main window will appear; on load it fetches and displays all contatos. + +--- + +## 🏗️ Architecture + +- 🏛️ **MVVM** with `ObservableObject`, `RelayCommand` +- 🧩 **DI** via `Microsoft.Extensions.Hosting` +- 💾 **Repositories** (`PostgresContatoRepository`) handle all DB I/O with Dapper +- 🔌 **Services** (`DatabaseService`) manage the Npgsql connection +- 🧠 **ViewModels** free of data-access logic: only orchestration + +--- + +## 🚀 Releases + +### 📆 21/05/2025: 🆕 Background & Export Features + +- 🎨 **Select Background…** Opens a file picker—choose any image (PNG, JPG, BMP). That image becomes your canvas. -- **Create Image…** +- 🖼️ **Create Image…** Saves the current DataGrid overlaid on the background as a single PNG. Uses WPF’s `RenderTargetBitmap` and `PngBitmapEncoder` under the hood. ---- \ No newline at end of file + +--- diff --git a/Services/ContactService.cs b/Services/ContactService.cs new file mode 100644 index 0000000..688989a --- /dev/null +++ b/Services/ContactService.cs @@ -0,0 +1,36 @@ +// BackgroundBuilder\Services\ContactService.cs +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using BackgroundBuilder.Models; +using BackgroundBuilder.Repositories; + +namespace BackgroundBuilder.Services +{ + public class ContactService(IContatoRepository repo) : IContactService + { + private readonly IContatoRepository _repo = repo; + + public Task> GetAllAsync() + => _repo.GetAllAsync(); + + public Task InsertUpdateAsync(Contato contato) + => _repo.InsertUpdateAsync(contato); + + public Task AddAsync(Contato contato) + => _repo.InsertUpdateAsync(contato); + + public Task DeleteAsync(string ramal) + => _repo.DeleteAsync(ramal); + + public IEnumerable GetComando(IEnumerable all) + => all.Where(c => c.IsComando); + + public IEnumerable GetSemComando(IEnumerable all) + => all.Where(c => !c.IsComando).OrderBy(x => x.Nome); + + public IEnumerable GetAniversarios(IEnumerable all) + => all.Where(c => c.Aniversario.HasValue).OrderBy(x => (x.Aniversario ?? DateTime.MinValue).Day).OrderBy(x => (x.Aniversario ?? DateTime.MinValue).Month); + } +} diff --git a/Services/DatabaseService.cs b/Services/DatabaseService.cs index 20ad871..179a036 100644 --- a/Services/DatabaseService.cs +++ b/Services/DatabaseService.cs @@ -4,10 +4,9 @@ using Npgsql; namespace BackgroundBuilder.Services { - public class DatabaseService(IConfiguration config) + public class DatabaseService() { - private readonly string _connString = config.GetConnectionString("ContatosDb") - ?? throw new InvalidOperationException("Missing connection string 'ContatosDb'."); + private readonly string _connString = "Host=192.168.10.248;Username=postgres;Password=gds21;Database=Smart Energia"; public NpgsqlConnection CreateConnection() { diff --git a/Services/IContactService.cs b/Services/IContactService.cs new file mode 100644 index 0000000..4bbb4a6 --- /dev/null +++ b/Services/IContactService.cs @@ -0,0 +1,18 @@ +// BackgroundBuilder\Services\IContactService.cs +using System.Collections.Generic; +using System.Threading.Tasks; +using BackgroundBuilder.Models; + +namespace BackgroundBuilder.Services +{ + public interface IContactService + { + Task InsertUpdateAsync(Contato contato); + Task DeleteAsync(string ramal); + + Task> GetAllAsync(); + IEnumerable GetComando(IEnumerable all); + IEnumerable GetSemComando(IEnumerable all); + IEnumerable GetAniversarios(IEnumerable all); + } +} diff --git a/Services/IImageService.cs b/Services/IImageService.cs index 56f9144..b4b8e5f 100644 --- a/Services/IImageService.cs +++ b/Services/IImageService.cs @@ -17,7 +17,7 @@ namespace BackgroundBuilder.Services /// • overlayPath (optional): overlay alone /// Returns the actual paths written. /// - Task<(string primaryPath, string? overlayPath)> SaveAsync( + Task SaveAsync( FrameworkElement overlay, BitmapImage background, string primaryPath, diff --git a/Services/IMessageService.cs b/Services/IMessageService.cs new file mode 100644 index 0000000..3fee25e --- /dev/null +++ b/Services/IMessageService.cs @@ -0,0 +1,10 @@ +using System.Windows; + +namespace BackgroundBuilder.Services +{ + public interface IMessageService + { + void Show(string message, string caption, MessageBoxButton buttons, MessageBoxImage icon); + MessageBoxResult ShowConfirm(string message, string caption, MessageBoxButton buttons, MessageBoxImage icon); + } +} \ No newline at end of file diff --git a/Services/ImageService.cs b/Services/ImageService.cs index bae1c37..0274814 100644 --- a/Services/ImageService.cs +++ b/Services/ImageService.cs @@ -26,14 +26,12 @@ namespace BackgroundBuilder.Services return await Task.FromResult(bmp); } - public async Task<(string primaryPath, string? overlayPath)> SaveAsync( + public async Task SaveAsync( FrameworkElement overlay, BitmapImage background, string primaryPath, string? overlayPath = null) { - var compositeBmp = RenderComposite(overlay, background, OverlayOffset); - SaveBitmap(compositeBmp, primaryPath); string? savedOverlayPath = null; if (!string.IsNullOrWhiteSpace(overlayPath)) @@ -43,7 +41,10 @@ namespace BackgroundBuilder.Services savedOverlayPath = overlayPath; } - return await Task.FromResult((primaryPath, savedOverlayPath)); + var compositeBmp = RenderComposite(overlay, background, OverlayOffset); + SaveBitmap(compositeBmp, primaryPath); + + await Task.FromResult((primaryPath, savedOverlayPath)); } /// @@ -54,11 +55,79 @@ namespace BackgroundBuilder.Services private static RenderTargetBitmap RenderComposite( FrameworkElement mainGrid, BitmapImage? background, - Thickness offset) + Thickness offset, + int taskbarHeight = 0) { - // Determine canvas size - int width = background?.PixelWidth ?? (int)mainGrid.ActualWidth; - int height = background?.PixelHeight ?? (int)mainGrid.ActualHeight; + double width = background?.PixelWidth ?? mainGrid.ActualWidth; + double height = background?.PixelHeight ?? mainGrid.ActualHeight; + + // Measure & arrange the mainGrid so ActualWidth/Height are valid + mainGrid.Measure(new Size(width, height)); + mainGrid.Arrange(new Rect(0, 0, mainGrid.DesiredSize.Width, mainGrid.DesiredSize.Height)); + + double gridWidth = mainGrid.ActualWidth > 0 ? mainGrid.ActualWidth : mainGrid.DesiredSize.Width; + double gridHeight = mainGrid.ActualHeight > 0 ? mainGrid.ActualHeight : mainGrid.DesiredSize.Height; + + double x; + double y; + double scale; + + if (background != null) + { + // Calculate max allowed size (half background, minus taskbar for height) + double maxWidth = width; + double maxHeight = (height - taskbarHeight); + + // Compute scale factor to fit within maxWidth/maxHeight, preserving aspect ratio + scale = Math.Min(1.0, Math.Min(maxWidth / gridWidth, maxHeight / gridHeight)); + + double scaledWidth = gridWidth * scale; + double scaledHeight = gridHeight * scale; + + // Place at lower right, offset + x = width - scaledWidth - offset.Right; + y = height - scaledHeight - offset.Bottom; + + gridWidth = scaledWidth + offset.Right; + gridHeight = scaledHeight + offset.Bottom; + } + else + { + // When rendering without background, use the mainGrid's own size and render at (0,0) + // --- Fix: Ensure full layout and hide scrollbars before rendering --- + mainGrid.UpdateLayout(); + + // If mainGrid is a ScrollViewer or contains one, hide scrollbars + if (mainGrid is System.Windows.Controls.ScrollViewer sv) + { + sv.HorizontalScrollBarVisibility = System.Windows.Controls.ScrollBarVisibility.Hidden; + sv.VerticalScrollBarVisibility = System.Windows.Controls.ScrollBarVisibility.Hidden; + } + // If mainGrid contains a DataGrid or similar, try to hide scrollbars + if (mainGrid is System.Windows.Controls.Panel panel) + { + foreach (var child in panel.Children) + { + if (child is System.Windows.Controls.DataGrid dg) + { + dg.HorizontalScrollBarVisibility = System.Windows.Controls.ScrollBarVisibility.Hidden; + dg.VerticalScrollBarVisibility = System.Windows.Controls.ScrollBarVisibility.Hidden; + } + } + } + + // Re-measure after hiding scrollbars + mainGrid.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); + mainGrid.Arrange(new Rect(0, 0, mainGrid.DesiredSize.Width, mainGrid.DesiredSize.Height)); + mainGrid.UpdateLayout(); + + gridWidth = mainGrid.ActualWidth > 0 ? mainGrid.ActualWidth : mainGrid.DesiredSize.Width; + gridHeight = mainGrid.ActualHeight > 0 ? mainGrid.ActualHeight : mainGrid.DesiredSize.Height; + width = (int)Math.Ceiling(gridWidth); + height = (int)Math.Ceiling(gridHeight); + x = 0; + y = 0; + } var dv = new DrawingVisual(); using (var ctx = dv.RenderOpen()) @@ -66,30 +135,20 @@ namespace BackgroundBuilder.Services // Draw background if provided if (background != null) { - ctx.DrawImage( - background, - new Rect(0, 0, width, height)); + ctx.DrawImage(background, new Rect(0, 0, width, height)); } - // Measure & arrange the mainGrid so ActualWidth/Height are valid - mainGrid.Measure(new Size(width, height)); - mainGrid.Arrange(new Rect(0, 0, mainGrid.DesiredSize.Width, mainGrid.DesiredSize.Height)); - - double ow = mainGrid.ActualWidth > 0 ? mainGrid.ActualWidth : mainGrid.DesiredSize.Width; - double oh = mainGrid.ActualHeight > 0 ? mainGrid.ActualHeight : mainGrid.DesiredSize.Height; - - double x = width - ow - offset.Left; - double y = height - oh - offset.Right; - - // Draw mainGrid at either origin or bottom-right with offset - var brush = new VisualBrush(mainGrid); - - ctx.DrawRectangle(brush, null, new Rect(x, y, ow, oh)); + // Draw mainGrid at calculated position, with scaling if needed + var brush = new VisualBrush(mainGrid) + { + Stretch = Stretch.Uniform, + }; + ctx.DrawRectangle(brush, null, new Rect(x, y, gridWidth, gridHeight)); } var rtb = new RenderTargetBitmap( - width, - height, + (int)width, + (int)height, 96, 96, PixelFormats.Pbgra32); @@ -103,8 +162,11 @@ namespace BackgroundBuilder.Services /// private static void SaveBitmap(RenderTargetBitmap bitmap, string path) { - // Ensure directory exists - Directory.CreateDirectory(Path.GetDirectoryName(path)!); + var directory = Path.GetDirectoryName(path); + if (!Directory.Exists(directory)) + { + Directory.CreateDirectory(directory!); + } using var fs = new FileStream(path, FileMode.Create, FileAccess.Write); var encoder = new PngBitmapEncoder(); diff --git a/Services/MessageService.cs b/Services/MessageService.cs new file mode 100644 index 0000000..b88d37f --- /dev/null +++ b/Services/MessageService.cs @@ -0,0 +1,13 @@ +using System.Windows; + +namespace BackgroundBuilder.Services +{ + public class MessageService : IMessageService + { + public void Show(string message, string caption, MessageBoxButton buttons, MessageBoxImage icon) + => MessageBox.Show(message, caption, buttons, icon); + + public MessageBoxResult ShowConfirm(string message, string caption, MessageBoxButton buttons, MessageBoxImage icon) + => MessageBox.Show(message, caption, buttons, icon); + } +} \ No newline at end of file diff --git a/Thumbs.db b/Thumbs.db new file mode 100644 index 0000000000000000000000000000000000000000..da31ada5b0e8d0b5bcb12ade02fcb88d1c7c34a4 GIT binary patch literal 5120 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 VoXEV$U1Q$Aff9!Z{)zv0?O$dc%<%vK literal 0 HcmV?d00001 diff --git a/ViewModels/MainWindowViewModel.cs b/ViewModels/MainWindowViewModel.cs index 8adef02..9893804 100644 --- a/ViewModels/MainWindowViewModel.cs +++ b/ViewModels/MainWindowViewModel.cs @@ -27,9 +27,11 @@ namespace BackgroundBuilder.ViewModels public ObservableCollection RawContatos { get; } = []; public ObservableCollection Comando { get; } = []; - public ObservableCollection ContatosSemCMD { get; } = []; public ObservableCollection Aniversarios { get; } = []; + public ObservableCollection ContatosSemCMDFirstHalf { get; } = new(); + public ObservableCollection ContatosSemCMDSecondHalf { get; } = new(); + private Contato? _selectedContato; public Contato? SelectedContato{ get => _selectedContato; set { _selectedContato = value; OnPropertyChanged(); DeleteCommand.RaiseCanExecuteChanged(); } } @@ -212,18 +214,30 @@ namespace BackgroundBuilder.ViewModels private void ApplyFilters() { Comando.Clear(); - ContatosSemCMD.Clear(); Aniversarios.Clear(); foreach (var item in RawContatos.Where(x => x.IsComando)) Comando.Add(item); - foreach (var item in RawContatos.Where(x => !x.IsComando).OrderBy(x => x.Nome)) - ContatosSemCMD.Add(item); - foreach (var item in RawContatos.Where(x => !x.IsComando).OrderBy(x => (x.Aniversario ?? DateTime.MinValue).Day).OrderBy(x => (x.Aniversario ?? DateTime.MinValue).Month)) Aniversarios.Add(item); + LoadContatosSemCMD(RawContatos.Where(x => !x.IsComando).OrderBy(x => x.Nome)); + } + + private void LoadContatosSemCMD(IEnumerable contatos) + { + ContatosSemCMDFirstHalf.Clear(); + ContatosSemCMDSecondHalf.Clear(); + var list = contatos.ToList(); + int half = (list.Count + 1) / 2; + for (int i = 0; i < list.Count; i++) + { + if (i < half) + ContatosSemCMDFirstHalf.Add(list[i]); + else + ContatosSemCMDSecondHalf.Add(list[i]); + } } } } \ No newline at end of file diff --git a/Views/MainWindow.xaml b/Views/MainWindow.xaml index 41ef816..419ed06 100644 --- a/Views/MainWindow.xaml +++ b/Views/MainWindow.xaml @@ -1,110 +1,39 @@  + Title="BackgroundBuilder" WindowStartupLocation="CenterScreen" WindowState="Maximized" MinHeight="950" MinWidth="1290" MaxHeight="1048" MaxWidth="2250"> - + - - - - - + + + + - - - - - - -