Adiciona importação de Excel e melhorias gerais

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.
This commit is contained in:
Giuliano Paschoalino 2025-08-20 17:56:47 -03:00
parent 1badf9db58
commit 5231c5cf9d
4 changed files with 85 additions and 19 deletions

View File

@ -15,6 +15,7 @@
<Content Include="smart.ico" /> <Content Include="smart.ico" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ClosedXML" Version="0.105.0" />
<PackageReference Include="Dapper" Version="2.1.66" /> <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" 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.Configuration.Json" Version="10.0.0-preview.3.25171.5" />

View File

@ -11,7 +11,7 @@ namespace BackgroundBuilder.Services
{ {
// no longer injected — use a private constant // no longer injected — use a private constant
private static readonly Thickness OverlayOffset = new(10, 58, 10, 58); private static readonly Thickness OverlayOffset = new(10, 53, 10, 53);
public async Task<BitmapImage> LoadAsync(string path) public async Task<BitmapImage> LoadAsync(string path)
{ {
@ -88,8 +88,8 @@ namespace BackgroundBuilder.Services
x = width - scaledWidth - offset.Right; x = width - scaledWidth - offset.Right;
y = height - scaledHeight - offset.Bottom; y = height - scaledHeight - offset.Bottom;
gridWidth = scaledWidth + offset.Right; gridWidth = scaledWidth;
gridHeight = scaledHeight + offset.Bottom; gridHeight = scaledHeight;
} }
else else
{ {
@ -161,6 +161,8 @@ namespace BackgroundBuilder.Services
/// Encodes the given bitmap as PNG and writes it to disk. /// Encodes the given bitmap as PNG and writes it to disk.
/// </summary> /// </summary>
private static void SaveBitmap(RenderTargetBitmap bitmap, string path) private static void SaveBitmap(RenderTargetBitmap bitmap, string path)
{
try
{ {
var directory = Path.GetDirectoryName(path); var directory = Path.GetDirectoryName(path);
if (!Directory.Exists(directory)) if (!Directory.Exists(directory))
@ -173,5 +175,10 @@ namespace BackgroundBuilder.Services
encoder.Frames.Add(BitmapFrame.Create(bitmap)); encoder.Frames.Add(BitmapFrame.Create(bitmap));
encoder.Save(fs); encoder.Save(fs);
} }
catch (Exception ex)
{
MessageBox.Show($"Erro ao salvar a imagem:\n{ex.Message}\n\nCaminho: {path}");
}
}
} }
} }

View File

@ -15,6 +15,7 @@ using BackgroundBuilder.Models;
using BackgroundBuilder.Repositories; using BackgroundBuilder.Repositories;
using BackgroundBuilder.Services; using BackgroundBuilder.Services;
using BackgroundBuilder.Utils; using BackgroundBuilder.Utils;
using ClosedXML.Excel;
using Microsoft.Win32; using Microsoft.Win32;
namespace BackgroundBuilder.ViewModels namespace BackgroundBuilder.ViewModels
@ -29,8 +30,8 @@ namespace BackgroundBuilder.ViewModels
public ObservableCollection<Contato> Comando { get; } = []; public ObservableCollection<Contato> Comando { get; } = [];
public ObservableCollection<Contato> Aniversarios { get; } = []; public ObservableCollection<Contato> Aniversarios { get; } = [];
public ObservableCollection<Contato> ContatosSemCMDFirstHalf { get; } = new(); public ObservableCollection<Contato> ContatosSemCMDFirstHalf { get; } = [];
public ObservableCollection<Contato> ContatosSemCMDSecondHalf { get; } = new(); public ObservableCollection<Contato> ContatosSemCMDSecondHalf { get; } = [];
private Contato? _selectedContato; private Contato? _selectedContato;
@ -60,6 +61,7 @@ namespace BackgroundBuilder.ViewModels
public ICommand RefreshCommand { get; } public ICommand RefreshCommand { get; }
public RelayCommand UpdateCommand { get; } public RelayCommand UpdateCommand { get; }
public RelayCommand ExportImageCommand { get; } public RelayCommand ExportImageCommand { get; }
public RelayCommand ImportExcelCommand { get; }
public MainWindowViewModel( public MainWindowViewModel(
IContatoRepository repo, IContatoRepository repo,
@ -78,6 +80,7 @@ namespace BackgroundBuilder.ViewModels
RefreshCommand = new RelayCommand(async _ => await LoadRawAsync()); RefreshCommand = new RelayCommand(async _ => await LoadRawAsync());
UpdateCommand = new RelayCommand(async _ => await UpdateAsync(), _ => SelectedContatos != null); UpdateCommand = new RelayCommand(async _ => await UpdateAsync(), _ => SelectedContatos != null);
ExportImageCommand = new RelayCommand(async _ => await RenderImageAsync(), _ => BackgroundImage != null && OverlayElement != null); ExportImageCommand = new RelayCommand(async _ => await RenderImageAsync(), _ => BackgroundImage != null && OverlayElement != null);
ImportExcelCommand = new RelayCommand(async _ => await ImportExcelAsync());
} }
public void RefreshTaskbarInfo() public void RefreshTaskbarInfo()
@ -161,7 +164,7 @@ namespace BackgroundBuilder.ViewModels
&& OverlayElement is FrameworkElement overlay && OverlayElement is FrameworkElement overlay
&& BackgroundImage is BitmapImage bg) && BackgroundImage is BitmapImage bg)
{ {
await _imageService.SaveAsync(overlay, bg, dlg.FileName, dlg.FileName.Replace(".", "_1.")); await _imageService.SaveAsync(overlay, bg, dlg.FileName, dlg.FileName.Replace(".png", "_1.png"));
} }
} }
@ -239,5 +242,52 @@ namespace BackgroundBuilder.ViewModels
ContatosSemCMDSecondHalf.Add(list[i]); ContatosSemCMDSecondHalf.Add(list[i]);
} }
} }
private async Task ImportExcelAsync()
{
var openFileDialog = new OpenFileDialog
{
Filter = "Excel Files|*.xlsx;*.xls"
};
if (openFileDialog.ShowDialog() != true)
return;
var filePath = openFileDialog.FileName;
var ramais = new List<Contato>();
using (var workbook = new XLWorkbook(filePath))
{
var ws = workbook.Worksheets.First();
var table = ws.Cell("B2").CurrentRegion;
foreach (var row in table.Rows().Skip(1)) // Skip header
{
var contato = new Contato
{
Ramal = row.Cell(3).GetString(),
Nome = row.Cell(1).GetString(),
Email = row.Cell(2).GetString(),
Area = row.Cell(4).GetString(),
IsComando = false
};
ramais.Add(contato);
}
table = ws.Cell("G2").CurrentRegion;
foreach (var row in table.Rows().Skip(1)) // Skip header
{
ramais.Find(x => x.Nome == row.Cell(1).GetString()).Aniversario = row.Cell(2).GetDateTime();
}
}
foreach (var contato in ramais)
{
await _repo.InsertUpdateAsync(contato);
}
await LoadRawAsync(); // Refresh UI
}
} }
} }

View File

@ -135,6 +135,7 @@
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<!-- Row 2: Image--> <!-- Row 2: Image-->
<Rectangle Grid.Column="0" <Rectangle Grid.Column="0"
@ -156,16 +157,23 @@
Padding="20,3,20,3" Padding="20,3,20,3"
Height="40" Height="40"
Margin="5"/> Margin="5"/>
<Button Content="Importar"
Command="{Binding ImportExcelCommand}"
Grid.Column="3"
FontWeight="Bold"
Padding="20,3,20,3"
Height="40"
Margin="5"/>
<Button Content="Recarregar Contatos" <Button Content="Recarregar Contatos"
Command="{Binding RefreshCommand}" Command="{Binding RefreshCommand}"
Grid.Column="3" Grid.Column="4"
FontWeight="Medium" FontWeight="Medium"
Padding="5" Padding="5"
Height="40" Height="40"
Margin="5"/> Margin="5"/>
<Button Content="+ Nova linha" <Button Content="+ Nova linha"
Command="{Binding AddCommand}" Command="{Binding AddCommand}"
Grid.Column = "4" Grid.Column = "5"
Foreground="Green" Foreground="Green"
FontWeight="Medium" FontWeight="Medium"
Padding="5" Padding="5"
@ -173,7 +181,7 @@
Margin="5"/> Margin="5"/>
<Button Content="- Deletar selecionada" <Button Content="- Deletar selecionada"
Command="{Binding DeleteCommand}" Command="{Binding DeleteCommand}"
Grid.Column = "5" Grid.Column = "6"
Foreground="Red" Foreground="Red"
FontWeight="Medium" FontWeight="Medium"
Padding="5" Padding="5"
@ -182,7 +190,7 @@
<Button Content="Salvar dados" <Button Content="Salvar dados"
Command="{Binding UpdateCommand}" Command="{Binding UpdateCommand}"
CommandParameter="RawGrid" CommandParameter="RawGrid"
Grid.Column = "6" Grid.Column = "7"
FontWeight="Medium" FontWeight="Medium"
Padding="5" Padding="5"
Height="40" Height="40"
@ -190,7 +198,7 @@
<Button Content="Criar Imagem -->" <Button Content="Criar Imagem -->"
Command="{Binding ExportImageCommand}" Command="{Binding ExportImageCommand}"
CommandParameter="MainGrid" CommandParameter="MainGrid"
Grid.Column = "7" Grid.Column = "8"
Foreground="Blue" Foreground="Blue"
FontWeight="Bold" FontWeight="Bold"
Padding="5" Padding="5"