Adicionada funcionalidade para importar contatos de arquivos Excel usando a biblioteca ClosedXML, incluindo o comando `ImportExcelCommand` e o método `ImportExcelAsync` na classe `MainWindowViewModel`. Ajustado o layout da interface gráfica para incluir um botão "Importar". Outras alterações incluem: - Adição de pacotes ao projeto (`ClosedXML`, `Dapper`, etc.). - Ajustes no cálculo de `gridWidth` e `gridHeight` em `ImageService`. - Alteração do valor da constante `OverlayOffset` em `ImageService`. - Refatoração do método `SaveBitmap` para tratamento de exceções. - Correção na substituição de extensão ao salvar imagens. - Reorganização de inicialização de coleções em `MainWindowViewModel`. Essas mudanças melhoram a funcionalidade e a consistência do projeto.
185 lines
7.2 KiB
C#
185 lines
7.2 KiB
C#
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, 53, 10, 53);
|
||
|
||
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 SaveAsync(
|
||
FrameworkElement overlay,
|
||
BitmapImage background,
|
||
string primaryPath,
|
||
string? overlayPath = null)
|
||
{
|
||
|
||
string? savedOverlayPath = null;
|
||
if (!string.IsNullOrWhiteSpace(overlayPath))
|
||
{
|
||
var overlayBmp = RenderComposite(overlay, null, new Thickness(0));
|
||
SaveBitmap(overlayBmp, overlayPath!);
|
||
savedOverlayPath = overlayPath;
|
||
}
|
||
|
||
var compositeBmp = RenderComposite(overlay, background, OverlayOffset);
|
||
SaveBitmap(compositeBmp, primaryPath);
|
||
|
||
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,
|
||
int taskbarHeight = 0)
|
||
{
|
||
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;
|
||
gridHeight = scaledHeight;
|
||
}
|
||
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())
|
||
{
|
||
// Draw background if provided
|
||
if (background != null)
|
||
{
|
||
ctx.DrawImage(background, new Rect(0, 0, width, height));
|
||
}
|
||
|
||
// 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(
|
||
(int)width,
|
||
(int)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)
|
||
{
|
||
try
|
||
{
|
||
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();
|
||
encoder.Frames.Add(BitmapFrame.Create(bitmap));
|
||
encoder.Save(fs);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
MessageBox.Show($"Erro ao salvar a imagem:\n{ex.Message}\n\nCaminho: {path}");
|
||
}
|
||
}
|
||
}
|
||
}
|