BD_empresa/MainWindow.xaml.cs
Giuliano Paschoalino 2f1c28e482 Melhorias na usabilidade e interação com o GridView
Substituído duplo-clique por botão para abrir pastas,
adicionada nova coluna com ícone de pasta e handler
`OpenFolderButton_Click`. Células agora usam `TextBox`
readonly para permitir seleção de texto. Melhorado o
comando de cópia (`Ctrl+C`) com tratamento de exceções
e mensagens de erro detalhadas.

Refatorado hit-test para suportar a nova estrutura de
células e adicionado fallback para capturar texto de
outras fontes. Ajustado evento `KeyDown` para abrir
pastas com validações adicionais. Melhorias gerais na
robustez e mensagens informativas ao usuário.
2025-09-30 17:23:44 -03:00

398 lines
16 KiB
C#

using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Reflection;
using System.Windows.Controls.Primitives;
namespace BD_empresa
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private string? _lastClickedCellText;
public MainWindow()
{
InitializeComponent();
// Caminho do banco de dados Access
string accessDbPath = "X:\\Middle\\Informativo Setorial\\Modelo Word\\BD1_dados cadastrais e faturas.accdb";
var accessService = new Data.AccessService($"Provider=Microsoft.ACE.OLEDB.12.0;Data Source={accessDbPath};Jet OLEDB:Database Password=gds21");
DataContext = new ViewModels.MainWindowViewModel(accessService);
}
/// <summary>
/// Captura texto quando clica com o botão esquerdo
/// </summary>
private void UnidadesListView_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (sender is ListView lv)
{
var pt = e.GetPosition(lv);
RecordCellTextFromPoint(lv, pt);
}
}
/// <summary>
/// Seleciona o item clicado com o botão direito e captura o texto da célula clicada
/// </summary>
private void UnidadesListView_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
if (sender is ListView lv)
{
var pt = e.GetPosition(lv);
var hit = VisualTreeHelper.HitTest(lv, pt);
var dep = hit?.VisualHit;
if (dep == null) return;
// Seleciona o item clicado
var lvi = FindAncestor<ListViewItem>(dep);
if (lvi != null) lvi.IsSelected = true;
// Se certo elemento TextBox foi clicado, foca ele (para permitir seleção/Ctrl+C)
var tbx = FindAncestor<TextBox>(dep) ?? FindDescendant<TextBox>(dep);
tbx?.Focus();
RecordCellTextFromPoint(lv, pt);
}
}
private void UnidadesListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
_lastClickedCellText = null;
}
/// <summary>
/// Executado quando o usuário pressiona Ctrl+C
/// </summary>
private void CopyCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
// sender normalmente é o ListView (porque definimos o CommandBinding nele).
var lv = sender as ListView ?? UnidadesListView;
CopyTextFromListView(lv);
}
/// <summary>
/// Executado pelo MenuItem do ContextMenu
/// </summary>
private void CopyMenu_Click(object sender, RoutedEventArgs e)
{
CopyTextFromListView(UnidadesListView);
}
/// <summary>
/// Copia para o clipboard com base no último texto clicado ou em fallback (primeira coluna)
/// </summary>
private void CopyTextFromListView(ListView? listView)
{
if (listView == null) return;
// 1) se o usuário clicou previamente em uma célula (direito/esquerdo), usamos esse texto
if (!string.IsNullOrEmpty(_lastClickedCellText))
{
try
{
Clipboard.SetText(_lastClickedCellText);
}
catch (Exception ex)
{
MessageBox.Show($"Erro ao copiar para o clipboard: {ex.Message}", "Erro", MessageBoxButton.OK, MessageBoxImage.Error);
}
return;
}
// 2) fallback: usa a primeira coluna (se houver) do item selecionado
if (listView.SelectedItem is Data.UnidadeSmart unidade)
{
var gv = listView.View as GridView;
if (gv?.Columns.Count > 0)
{
var firstCol = gv.Columns[0];
// tenta obter o DisplayMemberBinding.Path
if (firstCol.DisplayMemberBinding is System.Windows.Data.Binding b && !string.IsNullOrEmpty(b.Path?.Path))
{
var prop = unidade.GetType().GetProperty(b.Path.Path, BindingFlags.Public | BindingFlags.Instance);
if (prop != null)
{
var value = prop.GetValue(unidade)?.ToString() ?? string.Empty;
try
{
Clipboard.SetText(value);
}
catch (Exception ex)
{
MessageBox.Show($"Erro ao copiar para o clipboard: {ex.Message}", "Erro", MessageBoxButton.OK, MessageBoxImage.Error);
}
return;
}
}
// se não tiver DisplayMemberBinding (ou falhar), tenta buscar visualmente o TextBlock do ListViewItem
var lvi = listView.ItemContainerGenerator.ContainerFromItem(unidade) as ListViewItem;
var tb = FindDescendant<TextBlock>(lvi);
if (tb != null)
{
try
{
Clipboard.SetText(tb.Text);
}
catch (Exception ex)
{
MessageBox.Show($"Erro ao copiar para o clipboard: {ex.Message}", "Erro", MessageBoxButton.OK, MessageBoxImage.Error);
}
return;
}
}
// 3) fallback final: ToString do objeto
try
{
Clipboard.SetText(unidade?.ToString() ?? string.Empty);
}
catch (Exception ex)
{
MessageBox.Show($"Erro ao copiar para o clipboard: {ex.Message}", "Erro", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
/// <summary>
/// Faz hit-test em point e tenta achar o TextBlock responsável pela célula clicada;
/// grava em _lastClickedCellText.
/// </summary>
private void RecordCellTextFromPoint(ListView listView, Point point)
{
_lastClickedCellText = null;
var hit = VisualTreeHelper.HitTest(listView, point);
var dep = hit?.VisualHit;
if (dep == null) return;
// 1) Procura TextBox (nossa célula agora é TextBox readonly)
var tbx = FindAncestor<TextBox>(dep) ?? FindDescendant<TextBox>(dep);
if (tbx != null)
{
// se houver seleção, prioriza a seleção
if (!string.IsNullOrEmpty(tbx.SelectedText))
_lastClickedCellText = tbx.SelectedText;
else
_lastClickedCellText = tbx.Text;
return;
}
// 2) Procura TextBlock (fallback)
var tblock = FindAncestor<TextBlock>(dep) ?? FindDescendant<TextBlock>(dep);
if (tblock != null)
{
_lastClickedCellText = tblock.Text;
return;
}
// 3) Fallback: procura container e, dentro dele, tenta TextBox primeiro, depois TextBlock
DependencyObject? container = FindAncestor<ContentPresenter>(dep);
container ??= FindAncestor<GridViewRowPresenter>(dep);
if (container != null)
{
var innerTbx = FindDescendant<TextBox>(container);
if (innerTbx != null)
{
_lastClickedCellText = !string.IsNullOrEmpty(innerTbx.SelectedText) ? innerTbx.SelectedText : innerTbx.Text;
return;
}
var innerTblock = FindDescendant<TextBlock>(container);
if (innerTblock != null)
{
_lastClickedCellText = innerTblock.Text;
return;
}
}
// 4) último recurso: se um ListViewItem foi selecionado, tenta pegar a primeira coluna via binding (fallback anterior)
if (listView.SelectedItem is Data.UnidadeSmart unidade)
{
var gv = listView.View as GridView;
if (gv?.Columns.Count > 0)
{
var firstCol = gv.Columns[0];
if (firstCol.DisplayMemberBinding is System.Windows.Data.Binding b && !string.IsNullOrEmpty(b.Path?.Path))
{
var prop = unidade.GetType().GetProperty(b.Path.Path, BindingFlags.Public | BindingFlags.Instance);
if (prop != null)
{
var value = prop.GetValue(unidade)?.ToString() ?? string.Empty;
_lastClickedCellText = value;
}
}
}
}
}
// Helpers (coloque-os se já não existirem):
private static T? FindAncestor<T>(DependencyObject? current) where T : DependencyObject
{
while (current != null)
{
if (current is T typed) return typed;
current = VisualTreeHelper.GetParent(current);
}
return null;
}
private static T? FindDescendant<T>(DependencyObject? root) where T : DependencyObject
{
if (root == null) return null;
var queue = new Queue<DependencyObject>();
queue.Enqueue(root);
while (queue.Count > 0)
{
var node = queue.Dequeue();
var childrenCount = VisualTreeHelper.GetChildrenCount(node);
for (int i = 0; i < childrenCount; i++)
{
var child = VisualTreeHelper.GetChild(node, i);
if (child is T found) return found;
queue.Enqueue(child);
}
}
return null;
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
txtEmpresaSearch.Focus();
}
private void UnidadeListView_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (sender is ListViewItem listViewItem && listViewItem.Content is Data.UnidadeSmart unidade && !string.IsNullOrWhiteSpace(unidade.Caminho_NFs))
{
string? parentDirectory = System.IO.Path.GetDirectoryName(unidade.Caminho_NFs);
if (!string.IsNullOrWhiteSpace(parentDirectory))
{
try
{
System.Diagnostics.Process.Start("explorer.exe", parentDirectory);
}
catch (System.Exception ex)
{
MessageBox.Show($"Não foi possível abrir a pasta: {ex.Message}", "Erro", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
}
private void UnidadeListView_EnterKeyDown(object sender, KeyEventArgs e)
{
try
{
if (e.Key != Key.Enter) { return; }
// DataContext do Button será a UnidadeSmart relacionada à linha
var lvi = sender as ListViewItem;
if (lvi?.Content is not Data.UnidadeSmart unidade)
{
MessageBox.Show("Não foi possível identificar a unidade.", "Erro", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
var caminho = unidade.Caminho_NFs;
if (string.IsNullOrWhiteSpace(caminho))
{
MessageBox.Show("Não há caminho definido para essa unidade.", "Aviso", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
string folderToOpen;
// Se o caminho for um arquivo (provavelmente um arquivo NFe), abre o diretório pai.
if (System.IO.File.Exists(caminho))
{
folderToOpen = System.IO.Path.GetDirectoryName(caminho) ?? caminho;
}
else
{
// tenta obter diretório do caminho (caso usuário tenha colocado um arquivo inexistente ou caminho parcial)
var parent = System.IO.Path.GetDirectoryName(caminho);
if (!string.IsNullOrWhiteSpace(parent) && System.IO.Directory.Exists(parent))
folderToOpen = parent;
else
{
MessageBox.Show($"O caminho informado não existe: {caminho}", "Erro", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
}
// Abre o Explorer na pasta encontrada (UseShellExecute = true para abrir paths corretamente)
var psi = new System.Diagnostics.ProcessStartInfo
{
FileName = "explorer.exe",
Arguments = $"\"{folderToOpen}\"",
UseShellExecute = true
};
System.Diagnostics.Process.Start(psi);
}
catch (System.Exception ex)
{
MessageBox.Show($"Não foi possível abrir a pasta: {ex.Message}", "Erro", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void OpenFolderButton_Click(object sender, RoutedEventArgs e)
{
try
{
// DataContext do Button será a UnidadeSmart relacionada à linha
var btn = sender as Button;
if (btn?.DataContext is not Data.UnidadeSmart unidade)
{
MessageBox.Show("Não foi possível identificar a unidade.", "Erro", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
var caminho = unidade.Caminho_NFs;
if (string.IsNullOrWhiteSpace(caminho))
{
MessageBox.Show("Não há caminho definido para essa unidade.", "Aviso", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
string folderToOpen;
// Se o caminho for um arquivo (provavelmente um arquivo NFe), abre o diretório pai.
if (System.IO.File.Exists(caminho))
{
folderToOpen = System.IO.Path.GetDirectoryName(caminho) ?? caminho;
}
else
{
// tenta obter diretório do caminho (caso usuário tenha colocado um arquivo inexistente ou caminho parcial)
var parent = System.IO.Path.GetDirectoryName(caminho);
if (!string.IsNullOrWhiteSpace(parent) && System.IO.Directory.Exists(parent))
folderToOpen = parent;
else
{
MessageBox.Show($"O caminho informado não existe: {caminho}", "Erro", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
}
// Abre o Explorer na pasta encontrada (UseShellExecute = true para abrir paths corretamente)
var psi = new System.Diagnostics.ProcessStartInfo
{
FileName = "explorer.exe",
Arguments = $"\"{folderToOpen}\"",
UseShellExecute = true
};
System.Diagnostics.Process.Start(psi);
}
catch (System.Exception ex)
{
MessageBox.Show($"Não foi possível abrir a pasta: {ex.Message}", "Erro", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
}