Adicionar arquivos de projeto.
This commit is contained in:
parent
f5f3b40120
commit
b979de68c5
53
App.xaml
Normal file
53
App.xaml
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<Application x:Class="BackgroundBuilder.App"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
Startup="OnStartup">
|
||||||
|
<Application.Resources>
|
||||||
|
<Style TargetType="DataGrid">
|
||||||
|
<Setter Property="AutoGenerateColumns" Value="True"/>
|
||||||
|
<Setter Property="CanUserAddRows" Value="False"/>
|
||||||
|
<Setter Property="CanUserDeleteRows" Value="False"/>
|
||||||
|
<Setter Property="CanUserReorderColumns" Value="False"/>
|
||||||
|
<Setter Property="CanUserResizeColumns" Value="False"/>
|
||||||
|
<Setter Property="CanUserSortColumns" Value="False"/>
|
||||||
|
<Setter Property="GridLinesVisibility" Value="None"/>
|
||||||
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
<Setter Property="BorderBrush" Value="Black"/>
|
||||||
|
<Setter Property="IsReadOnly" Value="True"/>
|
||||||
|
<Setter Property="FontSize" Value="16"/>
|
||||||
|
<Setter Property="RowBackground" Value="GhostWhite"/>
|
||||||
|
<Setter Property="AlternatingRowBackground" Value="#387f79"/>
|
||||||
|
<Setter Property="Margin" Value="5"/>
|
||||||
|
<Setter Property="FontStyle" Value="Italic" />
|
||||||
|
<Setter Property="FontWeight" Value="Medium" />
|
||||||
|
<Setter Property="HeadersVisibility" Value="Column" />
|
||||||
|
<Setter Property="TextBlock.TextAlignment" Value="Center"/>
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||||
|
<Setter Property="VerticalAlignment" Value="Bottom"/>
|
||||||
|
<Setter Property="IsHitTestVisible" Value="False"/>
|
||||||
|
</Style>
|
||||||
|
<Style TargetType="DataGridColumnHeader">
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||||
|
<Setter Property="Background" Value="#387f79"/>
|
||||||
|
<Setter Property="Foreground" Value="White"/>
|
||||||
|
<Setter Property="Padding" Value="5"/>
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||||
|
</Style>
|
||||||
|
<Style TargetType="DataGridCell">
|
||||||
|
<Setter Property="FontWeight" Value="Medium" />
|
||||||
|
<Setter Property="Margin" Value="10,0,10,0"/>
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||||
|
</Style>
|
||||||
|
<Style TargetType="DataGridRow">
|
||||||
|
<Setter Property="Foreground" Value="Black"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="ItemsControl.AlternationIndex" Value="1">
|
||||||
|
<Setter Property="Foreground" Value="White"/>
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
<Style TargetType="Button">
|
||||||
|
<Setter Property="FontSize" Value="16"/>
|
||||||
|
</Style>
|
||||||
|
</Application.Resources>
|
||||||
|
</Application>
|
||||||
54
App.xaml.cs
Normal file
54
App.xaml.cs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
using System;
|
||||||
|
using System.Windows;
|
||||||
|
using BackgroundBuilder.Repositories;
|
||||||
|
using BackgroundBuilder.Services;
|
||||||
|
using BackgroundBuilder.ViewModels;
|
||||||
|
using BackgroundBuilder.Views;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
|
||||||
|
namespace BackgroundBuilder
|
||||||
|
{
|
||||||
|
public partial class App : Application
|
||||||
|
{
|
||||||
|
private readonly IHost _host;
|
||||||
|
|
||||||
|
public App()
|
||||||
|
{
|
||||||
|
_host = Host.CreateDefaultBuilder()
|
||||||
|
.ConfigureAppConfiguration(cfg =>
|
||||||
|
cfg.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true))
|
||||||
|
.ConfigureServices((_, services) =>
|
||||||
|
{
|
||||||
|
// Database & repository
|
||||||
|
services.AddSingleton<DatabaseService>();
|
||||||
|
services.AddScoped<IContatoRepository, PostgresContatoRepository>();
|
||||||
|
|
||||||
|
// New image service
|
||||||
|
services.AddSingleton<IImageService, ImageService>();
|
||||||
|
|
||||||
|
// New taskbar service
|
||||||
|
services.AddSingleton<ITaskbarService, TaskbarService>();
|
||||||
|
|
||||||
|
// VM & View
|
||||||
|
services.AddTransient<MainWindowViewModel>();
|
||||||
|
services.AddTransient<MainWindow>();
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnStartup(object sender, StartupEventArgs e)
|
||||||
|
{
|
||||||
|
await _host.StartAsync();
|
||||||
|
var window = _host.Services.GetRequiredService<MainWindow>();
|
||||||
|
window.Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async void OnExit(ExitEventArgs e)
|
||||||
|
{
|
||||||
|
using (_host) { await _host.StopAsync(); }
|
||||||
|
base.OnExit(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
AssemblyInfo.cs
Normal file
10
AssemblyInfo.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
[assembly: ThemeInfo(
|
||||||
|
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||||
|
//(used if a resource is not found in the page,
|
||||||
|
// or application resource dictionaries)
|
||||||
|
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||||
|
//(used if a resource is not found in the page,
|
||||||
|
// app, or any theme specific resource dictionaries)
|
||||||
|
)]
|
||||||
29
BackgroundBuilder.csproj
Normal file
29
BackgroundBuilder.csproj
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<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>
|
||||||
|
|
||||||
|
</Project>
|
||||||
22
BackgroundBuilder.sln
Normal file
22
BackgroundBuilder.sln
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.12.35527.113 d17.12
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BackgroundBuilder", "BackgroundBuilder.csproj", "{4AC89EE0-00B5-47D2-924B-FD65DD09E67B}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{4AC89EE0-00B5-47D2-924B-FD65DD09E67B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{4AC89EE0-00B5-47D2-924B-FD65DD09E67B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{4AC89EE0-00B5-47D2-924B-FD65DD09E67B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{4AC89EE0-00B5-47D2-924B-FD65DD09E67B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
26
Converters/DateNoDotConverter.cs
Normal file
26
Converters/DateNoDotConverter.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows.Data;
|
||||||
|
|
||||||
|
namespace BackgroundBuilder.Converters
|
||||||
|
{
|
||||||
|
public class DateNoDotConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is DateTime dt)
|
||||||
|
{
|
||||||
|
// get "MMM" then TrimEnd('.'):
|
||||||
|
string month = culture
|
||||||
|
.DateTimeFormat
|
||||||
|
.GetAbbreviatedMonthName(dt.Month)
|
||||||
|
.TrimEnd('.');
|
||||||
|
return $"{dt:dd}/{month}";
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
=> throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
14
Models/Contato.cs
Normal file
14
Models/Contato.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace BackgroundBuilder.Models
|
||||||
|
{
|
||||||
|
public class Contato
|
||||||
|
{
|
||||||
|
public string Ramal { get; set; } = string.Empty;
|
||||||
|
public string? Nome { get; set; } = string.Empty;
|
||||||
|
public string? Email { get; set; }
|
||||||
|
public string? Area { get; set; }
|
||||||
|
public DateTime? Aniversario { get; set; }
|
||||||
|
public bool IsComando { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
46
README.md
Normal file
46
README.md
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# BackgroundBuilder
|
||||||
|
|
||||||
|
An MVVM WPF application (.NET 6) providing an Excel-like editor for the `contatos` table in PostgreSQL.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- .NET 6 SDK
|
||||||
|
- PostgreSQL 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 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
|
||||||
|
|
||||||
|
- **Select Background…**
|
||||||
|
Opens a file picker—choose any image (PNG, JPG, BMP). That image becomes your canvas.
|
||||||
|
|
||||||
|
- **Create Image…**
|
||||||
|
Saves the current DataGrid overlaid on the background as a single PNG.
|
||||||
|
Uses WPF’s `RenderTargetBitmap` and `PngBitmapEncoder` under the hood.
|
||||||
|
---
|
||||||
13
Repositories/IContatoRepository.cs
Normal file
13
Repositories/IContatoRepository.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BackgroundBuilder.Models;
|
||||||
|
|
||||||
|
namespace BackgroundBuilder.Repositories
|
||||||
|
{
|
||||||
|
public interface IContatoRepository
|
||||||
|
{
|
||||||
|
Task<IEnumerable<Contato>> GetAllAsync();
|
||||||
|
Task InsertUpdateAsync(Contato c);
|
||||||
|
Task DeleteAsync(string ramal);
|
||||||
|
}
|
||||||
|
}
|
||||||
71
Repositories/PostgresContatoRepository.cs
Normal file
71
Repositories/PostgresContatoRepository.cs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Dapper;
|
||||||
|
using Npgsql;
|
||||||
|
using BackgroundBuilder.Models;
|
||||||
|
using BackgroundBuilder.Services;
|
||||||
|
|
||||||
|
namespace BackgroundBuilder.Repositories
|
||||||
|
{
|
||||||
|
public class PostgresContatoRepository(DatabaseService db) : IContatoRepository
|
||||||
|
{
|
||||||
|
private readonly DatabaseService _db = db;
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Contato>> GetAllAsync()
|
||||||
|
{
|
||||||
|
using var conn = _db.CreateConnection();
|
||||||
|
const string sql = @"
|
||||||
|
SELECT
|
||||||
|
ramal AS Ramal,
|
||||||
|
nome AS Nome,
|
||||||
|
email AS Email,
|
||||||
|
area AS Area,
|
||||||
|
aniversario AS Aniversario,
|
||||||
|
""isComando"" AS IsComando
|
||||||
|
FROM contatos;";
|
||||||
|
return await conn.QueryAsync<Contato>(sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddAsync(Contato c)
|
||||||
|
{
|
||||||
|
using var conn = _db.CreateConnection();
|
||||||
|
const string sql = @"
|
||||||
|
INSERT INTO contatos
|
||||||
|
(ramal, nome, email, area, aniversario, ""isComando"")
|
||||||
|
VALUES
|
||||||
|
(@Ramal, @Nome, @Email, @Area, @Aniversario, @IsComando);";
|
||||||
|
await conn.ExecuteAsync(sql, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteAsync(string ramal)
|
||||||
|
{
|
||||||
|
using var conn = _db.CreateConnection();
|
||||||
|
const string sql = "DELETE FROM contatos WHERE ramal=@Ramal;";
|
||||||
|
await conn.ExecuteAsync(sql, new { Ramal = ramal });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InsertUpdateAsync(Contato c)
|
||||||
|
{
|
||||||
|
using var conn = _db.CreateConnection();
|
||||||
|
string Ramal = $"'{c.Ramal}'";
|
||||||
|
string Nome = c.Nome is null ? "null" : $"'{c.Nome}'";
|
||||||
|
string Email = c.Email is null ? "null" : $"'{c.Email}'";
|
||||||
|
string Area = c.Area == "" ? "null" : $"'{c.Area}'";
|
||||||
|
string Aniversario = c.Aniversario is null ? "null" : $"'{c.Aniversario:yyyy-MM-dd}'";
|
||||||
|
string IsComando = c.IsComando ? "true" : "false";
|
||||||
|
|
||||||
|
string sql = @$"
|
||||||
|
INSERT INTO contatos
|
||||||
|
(ramal, nome, email, area, aniversario, ""isComando"")
|
||||||
|
VALUES
|
||||||
|
({Ramal}, {Nome}, {Email}, {Area}, {Aniversario:YYYY-MM-DD}, {IsComando})
|
||||||
|
ON CONFLICT (ramal) DO UPDATE SET
|
||||||
|
nome = EXCLUDED.nome,
|
||||||
|
email = EXCLUDED.email,
|
||||||
|
area = EXCLUDED.area,
|
||||||
|
aniversario = EXCLUDED.aniversario,
|
||||||
|
""isComando"" = EXCLUDED.""isComando"";";
|
||||||
|
await conn.ExecuteAsync(sql);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
Services/DatabaseService.cs
Normal file
19
Services/DatabaseService.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Npgsql;
|
||||||
|
|
||||||
|
namespace BackgroundBuilder.Services
|
||||||
|
{
|
||||||
|
public class DatabaseService(IConfiguration config)
|
||||||
|
{
|
||||||
|
private readonly string _connString = config.GetConnectionString("ContatosDb")
|
||||||
|
?? throw new InvalidOperationException("Missing connection string 'ContatosDb'.");
|
||||||
|
|
||||||
|
public NpgsqlConnection CreateConnection()
|
||||||
|
{
|
||||||
|
var conn = new NpgsqlConnection(_connString);
|
||||||
|
conn.Open();
|
||||||
|
return conn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
Services/IImageService.cs
Normal file
26
Services/IImageService.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
|
||||||
|
namespace BackgroundBuilder.Services
|
||||||
|
{
|
||||||
|
public interface IImageService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Loads an image from disk.
|
||||||
|
/// </summary>
|
||||||
|
Task<BitmapImage> LoadAsync(string path);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Renders the background + overlay and writes two PNGs:
|
||||||
|
/// • primaryPath: composite of background+overlay
|
||||||
|
/// • overlayPath (optional): overlay alone
|
||||||
|
/// Returns the actual paths written.
|
||||||
|
/// </summary>
|
||||||
|
Task<(string primaryPath, string? overlayPath)> SaveAsync(
|
||||||
|
FrameworkElement overlay,
|
||||||
|
BitmapImage background,
|
||||||
|
string primaryPath,
|
||||||
|
string? overlayPath = null);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
Services/ITaskbarService.cs
Normal file
13
Services/ITaskbarService.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
namespace BackgroundBuilder.Services
|
||||||
|
{
|
||||||
|
///
|
||||||
|
/// Defines methods for querying the Windows taskbar size and orientation.
|
||||||
|
///
|
||||||
|
public interface ITaskbarService
|
||||||
|
{
|
||||||
|
/// Gets the current thickness (height if horizontal, width if vertical) of the taskbar in pixels.
|
||||||
|
int GetTaskbarSize();
|
||||||
|
/// <summary>Returns true when the taskbar is horizontal, false when vertical.</summary>
|
||||||
|
bool IsHorizontal();
|
||||||
|
}
|
||||||
|
}
|
||||||
115
Services/ImageService.cs
Normal file
115
Services/ImageService.cs
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
|
||||||
|
namespace BackgroundBuilder.Services
|
||||||
|
{
|
||||||
|
public class ImageService : IImageService
|
||||||
|
{
|
||||||
|
|
||||||
|
// no longer injected — use a private constant
|
||||||
|
private static readonly Thickness OverlayOffset = new(10, 58, 10, 58);
|
||||||
|
|
||||||
|
public async Task<BitmapImage> LoadAsync(string path)
|
||||||
|
{
|
||||||
|
// Load image from disk into BitmapImage
|
||||||
|
var bmp = new BitmapImage();
|
||||||
|
using var stream = File.OpenRead(path);
|
||||||
|
bmp.BeginInit();
|
||||||
|
bmp.UriSource = new Uri(path, UriKind.Absolute);
|
||||||
|
bmp.CacheOption = BitmapCacheOption.OnLoad;
|
||||||
|
bmp.EndInit();
|
||||||
|
// no real async work—return completed task
|
||||||
|
return await Task.FromResult(bmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(string primaryPath, string? overlayPath)> 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))
|
||||||
|
{
|
||||||
|
var overlayBmp = RenderComposite(overlay, null, new Thickness(0));
|
||||||
|
SaveBitmap(overlayBmp, overlayPath!);
|
||||||
|
savedOverlayPath = overlayPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await Task.FromResult((primaryPath, savedOverlayPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Renders a <paramref name="background"/> plus the <paramref name="mainGrid"/>
|
||||||
|
/// at bottom‐right offset by <paramref name="offset"/>. If background is null,
|
||||||
|
/// only the mainGrid is rendered at (0,0).
|
||||||
|
/// </summary>
|
||||||
|
private static RenderTargetBitmap RenderComposite(
|
||||||
|
FrameworkElement mainGrid,
|
||||||
|
BitmapImage? background,
|
||||||
|
Thickness offset)
|
||||||
|
{
|
||||||
|
// Determine canvas size
|
||||||
|
int width = background?.PixelWidth ?? (int)mainGrid.ActualWidth;
|
||||||
|
int height = background?.PixelHeight ?? (int)mainGrid.ActualHeight;
|
||||||
|
|
||||||
|
var dv = new DrawingVisual();
|
||||||
|
using (var ctx = dv.RenderOpen())
|
||||||
|
{
|
||||||
|
// Draw background if provided
|
||||||
|
if (background != null)
|
||||||
|
{
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
var rtb = new RenderTargetBitmap(
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
96,
|
||||||
|
96,
|
||||||
|
PixelFormats.Pbgra32);
|
||||||
|
|
||||||
|
rtb.Render(dv);
|
||||||
|
return rtb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Encodes the given bitmap as PNG and writes it to disk.
|
||||||
|
/// </summary>
|
||||||
|
private static void SaveBitmap(RenderTargetBitmap bitmap, string path)
|
||||||
|
{
|
||||||
|
// Ensure directory exists
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
|
||||||
|
|
||||||
|
using var fs = new FileStream(path, FileMode.Create, FileAccess.Write);
|
||||||
|
var encoder = new PngBitmapEncoder();
|
||||||
|
encoder.Frames.Add(BitmapFrame.Create(bitmap));
|
||||||
|
encoder.Save(fs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
54
Services/TaskbarService.cs
Normal file
54
Services/TaskbarService.cs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace BackgroundBuilder.Services
|
||||||
|
{
|
||||||
|
///
|
||||||
|
/// Implementation of using P/Invoke (SHAppBarMessage).
|
||||||
|
///
|
||||||
|
public class TaskbarService : ITaskbarService
|
||||||
|
{
|
||||||
|
#region WinAPI
|
||||||
|
[DllImport("shell32.dll")]
|
||||||
|
private static extern IntPtr SHAppBarMessage(uint msg, ref APPBARDATA data);
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
private struct APPBARDATA
|
||||||
|
{
|
||||||
|
public uint cbSize;
|
||||||
|
public IntPtr hWnd;
|
||||||
|
public uint uCallbackMessage;
|
||||||
|
public uint uEdge;
|
||||||
|
public RECT rc;
|
||||||
|
public IntPtr lParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
private struct RECT { public int left, top, right, bottom; }
|
||||||
|
|
||||||
|
private const uint ABM_GETTASKBARPOS = 5;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public int GetTaskbarSize()
|
||||||
|
{
|
||||||
|
var data = new APPBARDATA { cbSize = (uint)Marshal.SizeOf<APPBARDATA>() };
|
||||||
|
if (SHAppBarMessage(ABM_GETTASKBARPOS, ref data) == IntPtr.Zero)
|
||||||
|
throw new InvalidOperationException("Failed to retrieve taskbar position.");
|
||||||
|
|
||||||
|
int thickness = IsHorizontal()
|
||||||
|
? Math.Abs(data.rc.bottom - data.rc.top)
|
||||||
|
: Math.Abs(data.rc.right - data.rc.left);
|
||||||
|
|
||||||
|
return thickness;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsHorizontal()
|
||||||
|
{
|
||||||
|
var data = new APPBARDATA { cbSize = (uint)Marshal.SizeOf<APPBARDATA>() };
|
||||||
|
SHAppBarMessage(ABM_GETTASKBARPOS, ref data);
|
||||||
|
int height = Math.Abs(data.rc.bottom - data.rc.top);
|
||||||
|
int width = Math.Abs(data.rc.right - data.rc.left);
|
||||||
|
|
||||||
|
return width > height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
Utils/ObservableObject.cs
Normal file
12
Utils/ObservableObject.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace BackgroundBuilder.Utils
|
||||||
|
{
|
||||||
|
public abstract class ObservableObject : INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
protected void OnPropertyChanged([CallerMemberName] string? propName = null)
|
||||||
|
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
|
||||||
|
}
|
||||||
|
}
|
||||||
18
Utils/RelayCommand.cs
Normal file
18
Utils/RelayCommand.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace BackgroundBuilder.Utils
|
||||||
|
{
|
||||||
|
public class RelayCommand(Action<object?> execute, Func<object?, bool>? canExecute = null) : ICommand
|
||||||
|
{
|
||||||
|
private readonly Action<object?> _execute = execute;
|
||||||
|
private readonly Func<object?, bool>? _canExecute = canExecute;
|
||||||
|
|
||||||
|
public bool CanExecute(object? parameter) => _canExecute?.Invoke(parameter) ?? true;
|
||||||
|
public void Execute(object? parameter) => _execute(parameter);
|
||||||
|
|
||||||
|
public event EventHandler? CanExecuteChanged;
|
||||||
|
public void RaiseCanExecuteChanged()
|
||||||
|
=> CanExecuteChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
229
ViewModels/MainWindowViewModel.cs
Normal file
229
ViewModels/MainWindowViewModel.cs
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using BackgroundBuilder.Models;
|
||||||
|
using BackgroundBuilder.Repositories;
|
||||||
|
using BackgroundBuilder.Services;
|
||||||
|
using BackgroundBuilder.Utils;
|
||||||
|
using Microsoft.Win32;
|
||||||
|
|
||||||
|
namespace BackgroundBuilder.ViewModels
|
||||||
|
{
|
||||||
|
public class MainWindowViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
private readonly IContatoRepository _repo;
|
||||||
|
private readonly IImageService _imageService;
|
||||||
|
private readonly ITaskbarService _taskbarService;
|
||||||
|
|
||||||
|
public ObservableCollection<Contato> RawContatos { get; } = [];
|
||||||
|
public ObservableCollection<Contato> Comando { get; } = [];
|
||||||
|
public ObservableCollection<Contato> ContatosSemCMD { get; } = [];
|
||||||
|
public ObservableCollection<Contato> Aniversarios { get; } = [];
|
||||||
|
|
||||||
|
|
||||||
|
private Contato? _selectedContato;
|
||||||
|
public Contato? SelectedContato{ get => _selectedContato; set { _selectedContato = value; OnPropertyChanged(); DeleteCommand.RaiseCanExecuteChanged(); } }
|
||||||
|
|
||||||
|
private ObservableCollection<Contato>? _selectedContatos;
|
||||||
|
public ObservableCollection<Contato> SelectedContatos { get => _selectedContatos ?? RawContatos; set { _selectedContatos = value; OnPropertyChanged(); UpdateCommand.RaiseCanExecuteChanged(); } }
|
||||||
|
|
||||||
|
private string? _caminhoBGImage;
|
||||||
|
public string? CaminhoBGImage{ get => _caminhoBGImage; set { _caminhoBGImage = value; OnPropertyChanged(); }}
|
||||||
|
|
||||||
|
private BitmapImage? _backgroundImage;
|
||||||
|
public BitmapImage? BackgroundImage{ get => _backgroundImage; private set { _backgroundImage = value; OnPropertyChanged(); }}
|
||||||
|
|
||||||
|
private int _taskbarSize;
|
||||||
|
public int TaskbarSize{ get => _taskbarSize; private set { _taskbarSize = value; OnPropertyChanged(); }}
|
||||||
|
|
||||||
|
private string _orientation = "Horizontal";
|
||||||
|
public string Orientation{ get => _orientation; private set { _orientation = value; OnPropertyChanged(); }}
|
||||||
|
|
||||||
|
public FrameworkElement? OverlayElement { get; set; }
|
||||||
|
|
||||||
|
// Commands
|
||||||
|
public RelayCommand DeleteCommand { get; }
|
||||||
|
public RelayCommand AddCommand { get; }
|
||||||
|
public RelayCommand SelectImageCommand { get; }
|
||||||
|
public ICommand RefreshCommand { get; }
|
||||||
|
public RelayCommand UpdateCommand { get; }
|
||||||
|
public RelayCommand ExportImageCommand { get; }
|
||||||
|
|
||||||
|
public MainWindowViewModel(
|
||||||
|
IContatoRepository repo,
|
||||||
|
IImageService imageService,
|
||||||
|
ITaskbarService taskbarService)
|
||||||
|
{
|
||||||
|
_repo = repo;
|
||||||
|
_imageService = imageService;
|
||||||
|
_taskbarService = taskbarService;
|
||||||
|
|
||||||
|
RefreshTaskbarInfo();
|
||||||
|
|
||||||
|
DeleteCommand = new RelayCommand(async _ => await DeleteAsync(), _ => SelectedContato != null);
|
||||||
|
AddCommand = new RelayCommand(async _ => await AddContactAsync());
|
||||||
|
SelectImageCommand = new RelayCommand(async _ => await SelectBackground());
|
||||||
|
RefreshCommand = new RelayCommand(async _ => await LoadRawAsync());
|
||||||
|
UpdateCommand = new RelayCommand(async _ => await UpdateAsync(), _ => SelectedContatos != null);
|
||||||
|
ExportImageCommand = new RelayCommand(async _ => await RenderImageAsync(), _ => BackgroundImage != null && OverlayElement != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RefreshTaskbarInfo()
|
||||||
|
{
|
||||||
|
TaskbarSize = _taskbarService.GetTaskbarSize();
|
||||||
|
Orientation = _taskbarService.IsHorizontal() ? "Horizontal" : "Vertical";
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DeleteAsync()
|
||||||
|
{
|
||||||
|
if (SelectedContato == null) return;
|
||||||
|
|
||||||
|
var result = MessageBox.Show(
|
||||||
|
"Delete selected record?",
|
||||||
|
"Confirm Delete",
|
||||||
|
MessageBoxButton.YesNo,
|
||||||
|
MessageBoxImage.Question);
|
||||||
|
|
||||||
|
if (result != MessageBoxResult.Yes) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _repo.DeleteAsync(SelectedContato.Ramal);
|
||||||
|
RawContatos.Remove(SelectedContato);
|
||||||
|
ApplyFilters();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
MessageBox.Show(
|
||||||
|
"Error deleting record.",
|
||||||
|
"Delete Failed",
|
||||||
|
MessageBoxButton.OK,
|
||||||
|
MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AddContactAsync()
|
||||||
|
{
|
||||||
|
var novo = new Contato { Ramal = "", Nome = "" };
|
||||||
|
RawContatos.Add(novo);
|
||||||
|
SelectedContato = novo; // Set the newly added contact as the selected one
|
||||||
|
await Task.CompletedTask; // preserve async signature
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SelectBackground()
|
||||||
|
{
|
||||||
|
var dlg = new OpenFileDialog
|
||||||
|
{
|
||||||
|
Filter = "Image Files|*.png;*.jpg;*.jpeg;*.bmp"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (dlg.ShowDialog() == true)
|
||||||
|
{
|
||||||
|
CaminhoBGImage = dlg.FileName;
|
||||||
|
BackgroundImage = await _imageService.LoadAsync(dlg.FileName);
|
||||||
|
if (!string.IsNullOrEmpty(CaminhoBGImage))
|
||||||
|
{
|
||||||
|
ExportImageCommand.RaiseCanExecuteChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LoadRawAsync()
|
||||||
|
{
|
||||||
|
var all = await _repo.GetAllAsync();
|
||||||
|
RawContatos.Clear();
|
||||||
|
foreach (var c in all.Where(x => x.IsComando)) RawContatos.Add(c);
|
||||||
|
foreach (var c in all.Where(x => !x.IsComando).OrderBy(x => x.Nome)) RawContatos.Add(c);
|
||||||
|
|
||||||
|
ApplyFilters();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RenderImageAsync()
|
||||||
|
{
|
||||||
|
var dlg = new SaveFileDialog
|
||||||
|
{
|
||||||
|
Filter = "PNG Image|*.png"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (dlg.ShowDialog() == true
|
||||||
|
&& OverlayElement is FrameworkElement overlay
|
||||||
|
&& BackgroundImage is BitmapImage bg)
|
||||||
|
{
|
||||||
|
await _imageService.SaveAsync(overlay, bg, dlg.FileName, dlg.FileName.Replace(".", "_1."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateAsync()
|
||||||
|
{
|
||||||
|
if (SelectedContatos == null) return;
|
||||||
|
|
||||||
|
var result = MessageBox.Show(
|
||||||
|
"Save selected records?",
|
||||||
|
"Confirm Save",
|
||||||
|
MessageBoxButton.YesNo,
|
||||||
|
MessageBoxImage.Question);
|
||||||
|
|
||||||
|
if (result != MessageBoxResult.Yes) return;
|
||||||
|
|
||||||
|
foreach (var contato in SelectedContatos)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(contato.Ramal)
|
||||||
|
|| string.IsNullOrWhiteSpace(contato.Nome))
|
||||||
|
{
|
||||||
|
MessageBox.Show(
|
||||||
|
"Ramal and Nome are required.",
|
||||||
|
"Validation Error",
|
||||||
|
MessageBoxButton.OK,
|
||||||
|
MessageBoxImage.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _repo.InsertUpdateAsync(contato);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
MessageBox.Show(
|
||||||
|
"Error saving record.",
|
||||||
|
"Save Failed",
|
||||||
|
MessageBoxButton.OK,
|
||||||
|
MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await LoadRawAsync();
|
||||||
|
MessageBox.Show(
|
||||||
|
"Concluído!",
|
||||||
|
"Confirm Save",
|
||||||
|
MessageBoxButton.OK,
|
||||||
|
MessageBoxImage.Information);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
184
Views/MainWindow.xaml
Normal file
184
Views/MainWindow.xaml
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
<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">
|
||||||
|
<Window.Resources>
|
||||||
|
<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">
|
||||||
|
<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 -->
|
||||||
|
<DataGrid x:Name="RawGrid"
|
||||||
|
Grid.Column="2"
|
||||||
|
ItemsSource="{Binding RawContatos}"
|
||||||
|
SelectedItem="{Binding SelectedContato}"
|
||||||
|
AutoGenerateColumns="False"
|
||||||
|
IsReadOnly="False"
|
||||||
|
IsHitTestVisible ="True"
|
||||||
|
FontSize="11"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Height="800"
|
||||||
|
AlternatingRowBackground="DarkGray">
|
||||||
|
<DataGrid.ColumnHeaderStyle>
|
||||||
|
<Style TargetType="DataGridColumnHeader">
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||||
|
<Setter Property="Background" Value="Gray"/>
|
||||||
|
<Setter Property="Foreground" Value="White"/>
|
||||||
|
<Setter Property="Padding" Value="5"/>
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||||
|
</Style>
|
||||||
|
</DataGrid.ColumnHeaderStyle>
|
||||||
|
<!-- your existing columns here -->
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTextColumn Header="Ramal" Binding="{Binding Ramal, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
<DataGridTextColumn Header="Nome" Binding="{Binding Nome, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
<DataGridTextColumn Header="Email" Binding="{Binding Email, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
<DataGridTextColumn Header="Área" Binding="{Binding Area, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
<DataGridTemplateColumn Header="Aniversário">
|
||||||
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<DatePicker SelectedDate="{Binding Aniversario, UpdateSourceTrigger=PropertyChanged}"/>
|
||||||
|
</DataTemplate>
|
||||||
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
|
</DataGridTemplateColumn>
|
||||||
|
<DataGridCheckBoxColumn Header="É Comando?" Binding="{Binding IsComando}" />
|
||||||
|
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Row 3: Buttons for actions -->
|
||||||
|
<Grid Grid.Row="3"
|
||||||
|
Margin="5">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Button Content="Recarregar Contatos"
|
||||||
|
Command="{Binding RefreshCommand}"
|
||||||
|
Grid.Column="0"
|
||||||
|
FontWeight="Medium"
|
||||||
|
Padding="5"
|
||||||
|
Margin="5"/>
|
||||||
|
<Button Content="+ Nova linha"
|
||||||
|
Command="{Binding AddCommand}"
|
||||||
|
Grid.Column = "1"
|
||||||
|
Foreground="Green"
|
||||||
|
FontWeight="Medium"
|
||||||
|
Padding="5"
|
||||||
|
Margin="5"/>
|
||||||
|
<Button Content="- Deletar selecionada"
|
||||||
|
Command="{Binding DeleteCommand}"
|
||||||
|
Grid.Column = "2"
|
||||||
|
Foreground="Red"
|
||||||
|
FontWeight="Medium"
|
||||||
|
Padding="5"
|
||||||
|
Margin="5"/>
|
||||||
|
<Button Content="Salvar dados"
|
||||||
|
Command="{Binding UpdateCommand}"
|
||||||
|
CommandParameter="RawGrid"
|
||||||
|
Grid.Column = "3"
|
||||||
|
FontWeight="Medium"
|
||||||
|
Padding="5"
|
||||||
|
Margin="5"/>
|
||||||
|
<Button Content="Criar Imagem -->"
|
||||||
|
Command="{Binding ExportImageCommand}"
|
||||||
|
CommandParameter="MainGrid"
|
||||||
|
Grid.Column = "4"
|
||||||
|
Foreground="Blue"
|
||||||
|
FontWeight="Bold"
|
||||||
|
Padding="5"
|
||||||
|
Margin="5"/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
29
Views/MainWindow.xaml.cs
Normal file
29
Views/MainWindow.xaml.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using BackgroundBuilder.Models;
|
||||||
|
using BackgroundBuilder.ViewModels;
|
||||||
|
|
||||||
|
namespace BackgroundBuilder.Views
|
||||||
|
{
|
||||||
|
public partial class MainWindow : Window
|
||||||
|
{
|
||||||
|
private readonly MainWindowViewModel _vm;
|
||||||
|
|
||||||
|
public MainWindow(MainWindowViewModel vm)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
this.Language = System.Windows.Markup.XmlLanguage.GetLanguage(System.Globalization.CultureInfo.CurrentUICulture.IetfLanguageTag);
|
||||||
|
|
||||||
|
_vm = vm;
|
||||||
|
DataContext = vm;
|
||||||
|
|
||||||
|
// Point the VM’s OverlayElement to our DataGrid
|
||||||
|
_vm.OverlayElement = MainGrid;
|
||||||
|
|
||||||
|
// Load contatos on window load
|
||||||
|
Loaded += async (_, __) => await _vm.LoadRawAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5
appsettings.json
Normal file
5
appsettings.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"ContatosDb": "Host=192.168.10.248;Username=postgres;Password=gds21;Database=Smart Energia"
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user