Import inicial: migração de arquivos da rede

This commit is contained in:
Giuliano Paschoalino 2025-07-14 11:44:57 -03:00
parent b979de68c5
commit 1badf9db58
21 changed files with 513 additions and 273 deletions

3
.gitignore vendored
View File

@ -360,4 +360,5 @@ MigrationBackup/
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
FodyWeavers.xsd
.history/

View File

@ -14,7 +14,7 @@
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="IsReadOnly" Value="True"/>
<Setter Property="FontSize" Value="16"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="RowBackground" Value="GhostWhite"/>
<Setter Property="AlternatingRowBackground" Value="#387f79"/>
<Setter Property="Margin" Value="5"/>

View File

@ -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

View File

@ -1,29 +1,44 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0-windows7.0</TargetFramework>
<UseWPF>true</UseWPF>
<RootNamespace>BackgroundBuilder</RootNamespace>
<AssemblyName>BackgroundBuilder</AssemblyName>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dapper" Version="2.1.66" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.0-preview.3.25171.5" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.0-preview.3.25171.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0-preview.3.25171.5" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0-preview.3.25171.5" />
<PackageReference Include="Npgsql" Version="9.0.3" />
<PackageReference Include="System.Drawing.Common" Version="10.0.0-preview.3.25173.2" />
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0-windows7.0</TargetFramework>
<UseWPF>true</UseWPF>
<RootNamespace>BackgroundBuilder</RootNamespace>
<AssemblyName>BackgroundBuilder</AssemblyName>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PackageIcon>smart.ico</PackageIcon>
<ApplicationIcon>smart.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Content Include="smart.ico" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Dapper" Version="2.1.66" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.0-preview.3.25171.5" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.0-preview.3.25171.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0-preview.3.25171.5" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0-preview.3.25171.5" />
<PackageReference Include="Npgsql" Version="9.0.3" />
<PackageReference Include="System.Drawing.Common" Version="10.0.0-preview.3.25173.2" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Settings.Designer.cs">
<DesignTimeSharedInput>True</DesignTimeSharedInput>
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<None Update="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<None Update="smart.ico">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
</Project>

26
Properties/Settings.Designer.cs generated Normal file
View File

@ -0,0 +1,26 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 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.
// </auto-generated>
//------------------------------------------------------------------------------
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;
}
}
}
}

View File

@ -0,0 +1,6 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
</SettingsFile>

View File

@ -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 WPFs `RenderTargetBitmap` and `PngBitmapEncoder` under the hood.
---
---

View File

@ -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<IEnumerable<Contato>> 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<Contato> GetComando(IEnumerable<Contato> all)
=> all.Where(c => c.IsComando);
public IEnumerable<Contato> GetSemComando(IEnumerable<Contato> all)
=> all.Where(c => !c.IsComando).OrderBy(x => x.Nome);
public IEnumerable<Contato> GetAniversarios(IEnumerable<Contato> all)
=> all.Where(c => c.Aniversario.HasValue).OrderBy(x => (x.Aniversario ?? DateTime.MinValue).Day).OrderBy(x => (x.Aniversario ?? DateTime.MinValue).Month);
}
}

View File

@ -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()
{

View File

@ -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<IEnumerable<Contato>> GetAllAsync();
IEnumerable<Contato> GetComando(IEnumerable<Contato> all);
IEnumerable<Contato> GetSemComando(IEnumerable<Contato> all);
IEnumerable<Contato> GetAniversarios(IEnumerable<Contato> all);
}
}

View File

@ -17,7 +17,7 @@ namespace BackgroundBuilder.Services
/// • overlayPath (optional): overlay alone
/// Returns the actual paths written.
/// </summary>
Task<(string primaryPath, string? overlayPath)> SaveAsync(
Task SaveAsync(
FrameworkElement overlay,
BitmapImage background,
string primaryPath,

View File

@ -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);
}
}

View File

@ -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));
}
/// <summary>
@ -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
/// </summary>
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();

View File

@ -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);
}
}

BIN
Thumbs.db Normal file

Binary file not shown.

View File

@ -27,9 +27,11 @@ namespace BackgroundBuilder.ViewModels
public ObservableCollection<Contato> RawContatos { get; } = [];
public ObservableCollection<Contato> Comando { get; } = [];
public ObservableCollection<Contato> ContatosSemCMD { get; } = [];
public ObservableCollection<Contato> Aniversarios { get; } = [];
public ObservableCollection<Contato> ContatosSemCMDFirstHalf { get; } = new();
public ObservableCollection<Contato> 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<Contato> 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]);
}
}
}
}

View File

@ -1,110 +1,39 @@
<Window x:Class="BackgroundBuilder.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:converters="clr-namespace:BackgroundBuilder.Converters"
Title="BackgroundBuilder" WindowStartupLocation="CenterScreen" WindowState="Maximized" MinHeight="950" MinWidth="1230">
Title="BackgroundBuilder" WindowStartupLocation="CenterScreen" WindowState="Maximized" MinHeight="950" MinWidth="1290" MaxHeight="1048" MaxWidth="2250">
<Window.Resources>
<converters:DateNoDotConverter x:Key="DateNoDotConverter" />
<converters:DateNoDotConverter x:Key="DateNoDotConverter"/>
</Window.Resources>
<Grid x:Name="RootGrid" Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Row 0: Background selection -->
<Grid Grid.Row="0">
<Border Grid.Row="0" BorderBrush="Black" BorderThickness="1"/>
<Border Grid.Row="1" BorderBrush="Black" BorderThickness="1,0,1,1"/>
<!-- Row 0: three side-by-side DataGrids -->
<Grid Grid.Row="0" HorizontalAlignment="Stretch" VerticalAlignment="Bottom">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Row 2: Image-->
<Rectangle Grid.Column="0"
Fill="Gainsboro"
Height="90"
Width="160"
>
</Rectangle>
<Image Source="{Binding BackgroundImage}" Height="90" Stretch="Uniform" Grid.Column="0"/>
<Button Content="Selecionar Imagem de Fundo…"
Command="{Binding SelectImageCommand}"
Grid.Column="1"
Padding="20,3,20,3"
Height="30"
Margin="5"/>
<TextBox Text="{Binding CaminhoBGImage}"
Grid.Column="2"
IsReadOnly="True"
Padding="20,3,20,3"
Height="30"
Margin="5"/>
</Grid>
<!-- Row 1: three side-by-side DataGrids -->
<Grid Grid.Row="1" HorizontalAlignment="Right" VerticalAlignment="Bottom">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" x:Name="MainGrid" HorizontalAlignment="Right" VerticalAlignment="Bottom">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Comando -->
<DataGrid Grid.Column="0"
ItemsSource="{Binding Comando}"
AutoGenerateColumns="False"
CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Ação" Binding="{Binding Nome}"/>
<DataGridTextColumn Header="Ramal" Binding="{Binding Ramal}"/>
</DataGrid.Columns>
</DataGrid>
<!-- ContatosSemCMD -->
<DataGrid Grid.Column="1"
ItemsSource="{Binding ContatosSemCMD}"
AutoGenerateColumns="False"
CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Nome" Binding="{Binding Nome}"/>
<DataGridTextColumn Header="Email" Binding="{Binding Email}"/>
<DataGridTextColumn Header="Ramal" Binding="{Binding Ramal}"/>
<DataGridTextColumn Header="Área" Binding="{Binding Area}"/>
</DataGrid.Columns>
</DataGrid>
<!-- Aniversarios -->
<DataGrid Grid.Column="2"
ItemsSource="{Binding Aniversarios}"
AutoGenerateColumns="False"
CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Nome" Binding="{Binding Nome}"/>
<DataGridTextColumn Header="Aniversário"
Binding="{Binding Aniversario,Converter={StaticResource DateNoDotConverter}}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
<!-- Row 3: DataGrid for RawContatos -->
<Border Grid.Column="0" BorderBrush="Black" BorderThickness="1"/>
<!-- Column 0: DataGrid for RawContatos -->
<DataGrid x:Name="RawGrid"
Grid.Column="2"
Grid.Column="0"
ItemsSource="{Binding RawContatos}"
SelectedItem="{Binding SelectedContato}"
AutoGenerateColumns="False"
IsReadOnly="False"
IsHitTestVisible ="True"
FontSize="11"
BorderBrush="Black"
BorderThickness="1"
VerticalAlignment="Top"
HorizontalAlignment="Center"
HorizontalAlignment="Left"
Height="800"
Panel.ZIndex="1"
SelectionChanged="ListView_SelectionChanged"
AlternatingRowBackground="DarkGray">
<DataGrid.ColumnHeaderStyle>
<Style TargetType="DataGridColumnHeader">
@ -115,6 +44,7 @@
<Setter Property="HorizontalAlignment" Value="Stretch"/>
</Style>
</DataGrid.ColumnHeaderStyle>
<!-- your existing columns here -->
<DataGrid.Columns>
<DataGridTextColumn Header="Ramal" Binding="{Binding Ramal, UpdateSourceTrigger=PropertyChanged}" />
@ -132,52 +62,139 @@
</DataGrid.Columns>
</DataGrid>
<!-- Column 1: DataGrid for MainGrid -->
<Grid Grid.Column="0" x:Name="MainGrid" HorizontalAlignment="Right" VerticalAlignment="Bottom">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Comando -->
<DataGrid Grid.Column="0"
ItemsSource="{Binding Comando}"
AutoGenerateColumns="False"
CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Ação" Binding="{Binding Nome}"/>
<DataGridTextColumn Header="Ramal" Binding="{Binding Ramal}"/>
</DataGrid.Columns>
</DataGrid>
<!-- ContatosSemCMD (First Half) -->
<DataGrid Grid.Column="1"
x:Name="ContatosSemCMDGrid1"
ItemsSource="{Binding ContatosSemCMDFirstHalf}"
AutoGenerateColumns="False"
CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Nome" Binding="{Binding Nome}"/>
<DataGridTextColumn Header="Email" Binding="{Binding Email}"/>
<DataGridTextColumn Header="Ramal" Binding="{Binding Ramal}"/>
<DataGridTextColumn Header="Área" Binding="{Binding Area}"/>
</DataGrid.Columns>
</DataGrid>
<!-- ContatosSemCMD (Second Half) -->
<DataGrid Grid.Column="2"
x:Name="ContatosSemCMDGrid2"
ItemsSource="{Binding ContatosSemCMDSecondHalf}"
AutoGenerateColumns="False"
CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Nome" Binding="{Binding Nome}"/>
<DataGridTextColumn Header="Email" Binding="{Binding Email}"/>
<DataGridTextColumn Header="Ramal" Binding="{Binding Ramal}"/>
<DataGridTextColumn Header="Área" Binding="{Binding Area}"/>
</DataGrid.Columns>
</DataGrid>
<!-- Aniversarios -->
<DataGrid Grid.Column="3"
ItemsSource="{Binding Aniversarios}"
AutoGenerateColumns="False"
CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Nome" Binding="{Binding Nome}"/>
<DataGridTextColumn Header="Aniversário"
Binding="{Binding Aniversario,Converter={StaticResource DateNoDotConverter}}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Grid>
<!-- Row 3: Buttons for actions -->
<Grid Grid.Row="3"
<!-- Row 1: Buttons for actions -->
<Grid Grid.Row="1"
Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Row 2: Image-->
<Rectangle Grid.Column="0"
Fill="Gainsboro"
Height="90"
Width="160"
>
</Rectangle>
<Image Source="{Binding BackgroundImage}" Height="90" Stretch="Uniform" Grid.Column="0"/>
<Button Content="Selecionar Imagem de Fundo…"
Command="{Binding SelectImageCommand}"
Grid.Column="1"
Padding="20,3,20,3"
Height="40"
Margin="5"/>
<TextBox Text="{Binding CaminhoBGImage}"
Grid.Column="2"
IsReadOnly="True"
Padding="20,3,20,3"
Height="40"
Margin="5"/>
<Button Content="Recarregar Contatos"
Command="{Binding RefreshCommand}"
Grid.Column="0"
Grid.Column="3"
FontWeight="Medium"
Padding="5"
Height="40"
Margin="5"/>
<Button Content="+ Nova linha"
Command="{Binding AddCommand}"
Grid.Column = "1"
Grid.Column = "4"
Foreground="Green"
FontWeight="Medium"
Padding="5"
Height="40"
Margin="5"/>
<Button Content="- Deletar selecionada"
Command="{Binding DeleteCommand}"
Grid.Column = "2"
Grid.Column = "5"
Foreground="Red"
FontWeight="Medium"
Padding="5"
Height="40"
Margin="5"/>
<Button Content="Salvar dados"
Command="{Binding UpdateCommand}"
CommandParameter="RawGrid"
Grid.Column = "3"
Grid.Column = "6"
FontWeight="Medium"
Padding="5"
Height="40"
Margin="5"/>
<Button Content="Criar Imagem -->"
Command="{Binding ExportImageCommand}"
CommandParameter="MainGrid"
Grid.Column = "4"
Grid.Column = "7"
Foreground="Blue"
FontWeight="Bold"
Padding="5"
Height="40"
Margin="5"/>
</Grid>
</Grid>

View File

@ -25,5 +25,13 @@ namespace BackgroundBuilder.Views
// Load contatos on window load
Loaded += async (_, __) => await _vm.LoadRawAsync();
}
private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (sender is DataGrid gridView && gridView.SelectedItem != null)
{
gridView.ScrollIntoView(gridView.SelectedItem);
}
}
}
}

View File

@ -1,5 +0,0 @@
{
"ConnectionStrings": {
"ContatosDb": "Host=192.168.10.248;Username=postgres;Password=gds21;Database=Smart Energia"
}
}

149
resume.md
View File

@ -1,5 +1,7 @@
DateNoDotConverter.cs:
[
# Resume
DateNoDotConverter.cs:
```cs
//BackgroundBuilder\Converters\DateNoDotConverter.cs
using System;
@ -29,9 +31,9 @@ namespace BackgroundBuilder.Converters
}
}
```
]
Contato.cs:
[
```cs
//BackgroundBuilder\Models\Contato.cs
using System;
@ -49,10 +51,9 @@ namespace BackgroundBuilder.Models
}
}
```
]
IContatoRepository.cs:
[
```cs
//BackgroundBuilder\Repositories\IContatoRepository.cs
using System.Collections.Generic;
@ -69,10 +70,9 @@ namespace BackgroundBuilder.Repositories
}
}
```
]
PostgresContatoRepository.cs:
[
```cs
//BackgroundBuilder\Repositories\PostgresContatoRepository.cs
using System.Collections.Generic;
@ -147,10 +147,9 @@ namespace BackgroundBuilder.Repositories
}
}
```
]
DatabaseService.cs:
[
```cs
//BackgroundBuilder\Services\DatabaseService.cs
using System;
@ -173,10 +172,9 @@ namespace BackgroundBuilder.Services
}
}
```
]
IImageService.cs:
[
```cs
//BackgroundBuilder\Services\IImageService.cs
using System.Threading.Tasks;
@ -206,10 +204,9 @@ namespace BackgroundBuilder.Services
}
}
```
]
ImageService.cs:
[
```cs
//BackgroundBuilder\Services\ImageService.cs
using System;
@ -328,10 +325,9 @@ namespace BackgroundBuilder.Services
}
}
```
]
ITaskbarService.cs:
[
```cs
//BackgroundBuilder\Services\ITaskbarService.cs
namespace BackgroundBuilder.Services
@ -348,10 +344,9 @@ namespace BackgroundBuilder.Services
}
}
```
]
TaskbarService.cs:
[
```cs
//BackgroundBuilder\Services\TaskbarService.cs
using System;
@ -409,10 +404,9 @@ namespace BackgroundBuilder.Services
}
}
```
]
ObservableObject.cs:
[
```cs
//BackgroundBuilder\Utils\ObservableObject.cs
using System.ComponentModel;
@ -428,10 +422,9 @@ namespace BackgroundBuilder.Utils
}
}
```
]
RelayCommand.cs:
[
```cs
//BackgroundBuilder\Utils\RelayCommand.cs
using System;
@ -453,10 +446,9 @@ namespace BackgroundBuilder.Utils
}
}
```
]
MainWindowViewModel.cs:
[
```cs
//BackgroundBuilder\ViewModels\MainWindowViewModel.cs
using System;
@ -689,15 +681,14 @@ namespace BackgroundBuilder.ViewModels
}
}
```
]
MainWindow.xaml
[
```xml
<!--BackgroundBuilder\Views\MainWindow.xaml-->
<Window x:Class="BackgroundBuilder.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:converters="clr-namespace:BackgroundBuilder.Converters"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:converters="clr-namespace:BackgroundBuilder.Converters"
Title="BackgroundBuilder" WindowStartupLocation="CenterScreen" WindowState="Maximized" MinHeight="950" MinWidth="1230">
<Window.Resources>
<converters:DateNoDotConverter x:Key="DateNoDotConverter" />
@ -880,10 +871,9 @@ MainWindow.xaml
</Grid>
</Window>
```
]
MainWindow.xaml.cs:
[
```cs
//BackgroundBuilder\Views\MainWindow.xaml.cs
using System.ComponentModel;
@ -916,10 +906,9 @@ namespace BackgroundBuilder.Views
}
}
```
]
App.xaml
[
```xml
<!--BackgroundBuilder\App.xaml-->
<Application x:Class="BackgroundBuilder.App"
@ -976,10 +965,9 @@ App.xaml
</Application.Resources>
</Application>
```
]
App.xaml.cs:
[
```cs
//BackgroundBuilder\App.xaml.cs
using System;
@ -1037,10 +1025,9 @@ namespace BackgroundBuilder
}
}
```
]
appsettings.json:
[
```json
//BackgroundBuilder\appsettings.json
{
@ -1049,56 +1036,72 @@ appsettings.json:
}
}
```
]
README.md:
[
```md
# 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 WPFs `RenderTargetBitmap` and `PngBitmapEncoder` under the hood.
---
```
]

BIN
smart.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB