Import inicial: migração de arquivos da rede
This commit is contained in:
parent
b979de68c5
commit
1badf9db58
1
.gitignore
vendored
1
.gitignore
vendored
@ -361,3 +361,4 @@ MigrationBackup/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
.history/
|
||||
2
App.xaml
2
App.xaml
@ -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"/>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -8,8 +8,12 @@
|
||||
<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" />
|
||||
@ -19,10 +23,21 @@
|
||||
<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>
|
||||
<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>
|
||||
|
||||
|
||||
26
Properties/Settings.Designer.cs
generated
Normal file
26
Properties/Settings.Designer.cs
generated
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
6
Properties/Settings.settings
Normal file
6
Properties/Settings.settings
Normal 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>
|
||||
57
README.md
57
README.md
@ -1,13 +1,19 @@
|
||||
# BackgroundBuilder
|
||||
|
||||
An MVVM WPF application (.NET 6) providing an Excel-like editor for the `contatos` table in PostgreSQL.
|
||||
[](https://dotnet.microsoft.com/) [](https://learn.microsoft.com/dotnet/desktop/wpf/) [](https://learn.microsoft.com/dotnet/desktop/wpf/xaml/) [](https://learn.microsoft.com/dotnet/csharp/) [](https://learn.microsoft.com/pt-br/dotnet/architecture/maui/mvvm) [](https://learn.microsoft.com/dotnet/core/extensions/dependency-injection) [](https://www.postgresql.org/) [](https://www.npgsql.org/) [](https://github.com/DapperLib/Dapper) [](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
|
||||
---
|
||||
|
||||
## 📑 Prerequisites
|
||||
|
||||
- [](https://dotnet.microsoft.com/)
|
||||
- [](https://www.postgresql.org/) **database** with table:
|
||||
|
||||
```sql
|
||||
CREATE TABLE public.contatos (
|
||||
ramal text PRIMARY KEY NOT NULL,
|
||||
nome text NOT NULL,
|
||||
@ -16,31 +22,44 @@ An MVVM WPF application (.NET 6) providing an Excel-like editor for the `contato
|
||||
aniversario date,
|
||||
"isComando" boolean NOT NULL
|
||||
);
|
||||
```
|
||||
## Setup
|
||||
1. Edit appsettings.json, set your ConnectionStrings:ContatosDb.
|
||||
2. In a terminal:
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 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.
|
||||
3. 🪟 The main window will appear; on load it fetches and displays all contatos.
|
||||
|
||||
---
|
||||
|
||||
### New Background & Export Features
|
||||
## 🏗️ Architecture
|
||||
|
||||
- **Select Background…**
|
||||
- 🏛️ **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.
|
||||
|
||||
---
|
||||
36
Services/ContactService.cs
Normal file
36
Services/ContactService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
{
|
||||
|
||||
18
Services/IContactService.cs
Normal file
18
Services/IContactService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
|
||||
10
Services/IMessageService.cs
Normal file
10
Services/IMessageService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
|
||||
13
Services/MessageService.cs
Normal file
13
Services/MessageService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
</Grid>
|
||||
|
||||
<!-- Row 3: Buttons for actions -->
|
||||
<Grid Grid.Row="3"
|
||||
Margin="5">
|
||||
<!-- 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"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</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 1: Buttons for actions -->
|
||||
<Grid Grid.Row="1"
|
||||
Margin="5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<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>
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"ContatosDb": "Host=192.168.10.248;Username=postgres;Password=gds21;Database=Smart Energia"
|
||||
}
|
||||
}
|
||||
117
resume.md
117
resume.md
@ -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,10 +681,9 @@ namespace BackgroundBuilder.ViewModels
|
||||
}
|
||||
}
|
||||
```
|
||||
]
|
||||
|
||||
MainWindow.xaml
|
||||
[
|
||||
|
||||
```xml
|
||||
<!--BackgroundBuilder\Views\MainWindow.xaml-->
|
||||
<Window x:Class="BackgroundBuilder.Views.MainWindow"
|
||||
@ -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,21 +1036,26 @@ appsettings.json:
|
||||
}
|
||||
}
|
||||
```
|
||||
]
|
||||
|
||||
README.md:
|
||||
[
|
||||
|
||||
```md
|
||||
# BackgroundBuilder
|
||||
|
||||
An MVVM WPF application (.NET 6) providing an Excel-like editor for the `contatos` table in PostgreSQL.
|
||||
[](https://dotnet.microsoft.com/) [](https://learn.microsoft.com/dotnet/desktop/wpf/) [](https://learn.microsoft.com/dotnet/desktop/wpf/xaml/) [](https://learn.microsoft.com/dotnet/csharp/) [](https://learn.microsoft.com/pt-br/dotnet/architecture/maui/mvvm) [](https://learn.microsoft.com/dotnet/core/extensions/dependency-injection) [](https://www.postgresql.org/) [](https://www.npgsql.org/) [](https://github.com/DapperLib/Dapper) [](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
|
||||
---
|
||||
|
||||
## 📑 Prerequisites
|
||||
|
||||
- [](https://dotnet.microsoft.com/)
|
||||
- [](https://www.postgresql.org/) **database** with table:
|
||||
|
||||
```sql
|
||||
CREATE TABLE public.contatos (
|
||||
ramal text PRIMARY KEY NOT NULL,
|
||||
nome text NOT NULL,
|
||||
@ -1072,33 +1064,44 @@ An MVVM WPF application (.NET 6) providing an Excel-like editor for the `contato
|
||||
aniversario date,
|
||||
"isComando" boolean NOT NULL
|
||||
);
|
||||
```
|
||||
## Setup
|
||||
1. Edit appsettings.json, set your ConnectionStrings:ContatosDb.
|
||||
2. In a terminal:
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 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.
|
||||
3. 🪟 The main window will appear; on load it fetches and displays all contatos.
|
||||
|
||||
---
|
||||
|
||||
### New Background & Export Features
|
||||
## 🏗️ Architecture
|
||||
|
||||
- **Select Background…**
|
||||
- 🏛️ **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.
|
||||
|
||||
---
|
||||
```
|
||||
]
|
||||
Loading…
x
Reference in New Issue
Block a user