Varias mejoras como auto asignar el foco si la aplaicacion ya esta ejecutandose. Modo transparente. Funcion de Pinned .
This commit is contained in:
commit
d3dfec8d95
|
@ -0,0 +1,418 @@
|
|||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
*.env
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
[Aa][Rr][Mm]64[Ee][Cc]/
|
||||
bld/
|
||||
[Oo]bj/
|
||||
[Oo]ut/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Build results on 'Bin' directories
|
||||
**/[Bb]in/*
|
||||
# Uncomment if you have tasks that rely on *.refresh files to move binaries
|
||||
# (https://github.com/github/gitignore/pull/3736)
|
||||
#!**/[Bb]in/*.refresh
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
*.trx
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Approval Tests result files
|
||||
*.received.*
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.idb
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
# but not Directory.Build.rsp, as it configures directory-level build defaults
|
||||
!Directory.Build.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.tlog
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
||||
*.vbp
|
||||
|
||||
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||
*.dsw
|
||||
*.dsp
|
||||
|
||||
# Visual Studio 6 technical files
|
||||
*.ncb
|
||||
*.aps
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
**/.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
**/.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
**/.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
**/__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
#tools/**
|
||||
#!tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
MSBuild_Logs/
|
||||
|
||||
# AWS SAM Build and Temporary Artifacts folder
|
||||
.aws-sam
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
**/.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
**/.localhistory/
|
||||
|
||||
# Visual Studio History (VSHistory) files
|
||||
.vshistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
**/.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
# VS Code files for those working on multiple tools
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
# Windows Installer files from build outputs
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
// Use IntelliSense to find out which attributes exist for C# debugging
|
||||
// Use hover for the description of the existing attributes
|
||||
// For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md
|
||||
"name": ".NET Core Launch (console)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "build",
|
||||
// If you have changed target frameworks, make sure to update the program path.
|
||||
"program": "${workspaceFolder}/bin/Debug/net8.0-windows/ShortcutsHelper.dll",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}",
|
||||
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
|
||||
"console": "internalConsole",
|
||||
"stopAtEntry": false
|
||||
},
|
||||
{
|
||||
"name": ".NET Core Attach",
|
||||
"type": "coreclr",
|
||||
"request": "attach"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"build",
|
||||
"${workspaceFolder}/ShortcutsHelper.sln",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary;ForceNoAlign"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "publish",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"publish",
|
||||
"${workspaceFolder}/ShortcutsHelper.sln",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary;ForceNoAlign"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "watch",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"watch",
|
||||
"run",
|
||||
"--project",
|
||||
"${workspaceFolder}/ShortcutsHelper.sln"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<Application x:Class="ShortcutsHelper.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:ShortcutsHelper"
|
||||
StartupUri="MainWindow.xaml">
|
||||
<Application.Resources>
|
||||
|
||||
</Application.Resources>
|
||||
</Application>
|
|
@ -0,0 +1,103 @@
|
|||
using System.Configuration;
|
||||
using System.Data;
|
||||
using System.Windows;
|
||||
using System.Threading;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System;
|
||||
|
||||
namespace ShortcutsHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : System.Windows.Application
|
||||
{
|
||||
private static Mutex? _mutex = null;
|
||||
private const string MutexName = "ShortcutsHelper_SingleInstance_Mutex";
|
||||
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
// Intentar crear un mutex para controlar instancia única
|
||||
bool createdNew;
|
||||
_mutex = new Mutex(true, MutexName, out createdNew);
|
||||
|
||||
if (!createdNew)
|
||||
{
|
||||
// Ya hay una instancia ejecutándose
|
||||
// Intentar activar la ventana existente
|
||||
ActivateExistingWindow();
|
||||
|
||||
// Cerrar esta instancia
|
||||
Shutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
// Primera instancia, continuar normalmente
|
||||
base.OnStartup(e);
|
||||
}
|
||||
|
||||
protected override void OnExit(ExitEventArgs e)
|
||||
{
|
||||
// Liberar el mutex al salir
|
||||
_mutex?.ReleaseMutex();
|
||||
_mutex?.Dispose();
|
||||
base.OnExit(e);
|
||||
}
|
||||
|
||||
private void ActivateExistingWindow()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Buscar el proceso de ShortcutsHelper que ya está ejecutándose
|
||||
var currentProcess = Process.GetCurrentProcess();
|
||||
var processes = Process.GetProcessesByName(currentProcess.ProcessName);
|
||||
|
||||
foreach (var process in processes)
|
||||
{
|
||||
// Saltar el proceso actual
|
||||
if (process.Id == currentProcess.Id)
|
||||
continue;
|
||||
|
||||
// Intentar activar la ventana principal del proceso
|
||||
if (process.MainWindowHandle != IntPtr.Zero)
|
||||
{
|
||||
// Si la ventana está minimizada, restaurarla
|
||||
if (IsIconic(process.MainWindowHandle))
|
||||
{
|
||||
ShowWindow(process.MainWindowHandle, SW_RESTORE);
|
||||
}
|
||||
|
||||
// Traer al frente y dar foco
|
||||
SetForegroundWindow(process.MainWindowHandle);
|
||||
BringWindowToTop(process.MainWindowHandle);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Si falla la activación, al menos mostrar un mensaje
|
||||
System.Windows.MessageBox.Show($"ShortcutsHelper ya está ejecutándose. Error al activar: {ex.Message}",
|
||||
"Aplicación ya en ejecución",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Information);
|
||||
}
|
||||
}
|
||||
|
||||
// APIs de Windows para manejo de ventanas
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool SetForegroundWindow(IntPtr hWnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool BringWindowToTop(IntPtr hWnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool IsIconic(IntPtr hWnd);
|
||||
|
||||
private const int SW_RESTORE = 9;
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
)]
|
|
@ -0,0 +1,45 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace ShortcutsHelper
|
||||
{
|
||||
public class BoolToPinConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is bool isPinned)
|
||||
{
|
||||
return isPinned ? "📌" : "📍";
|
||||
}
|
||||
return "📍";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public class BoolToVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is bool boolValue)
|
||||
{
|
||||
return boolValue ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is Visibility visibility)
|
||||
{
|
||||
return visibility == Visibility.Visible;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
<Window x:Class="ShortcutsHelper.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:ShortcutsHelper"
|
||||
xmlns:vm="clr-namespace:ShortcutsHelper.ViewModels"
|
||||
mc:Ignorable="d"
|
||||
Title="Shortcuts Helper" Height="400" Width="600" Topmost="True"
|
||||
ResizeMode="CanResize" WindowStyle="ToolWindow">
|
||||
<Window.Resources>
|
||||
<!-- Converter para el ícono de Pin -->
|
||||
<local:BoolToPinConverter x:Key="BoolToPinConverter"/>
|
||||
<!-- Converter para visibilidad basada en boolean -->
|
||||
<local:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
|
||||
</Window.Resources>
|
||||
<Window.DataContext>
|
||||
<vm:MainViewModel/>
|
||||
</Window.DataContext>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Header con aplicación actual y botón de Pin -->
|
||||
<Border Grid.Row="0" Background="DarkBlue" Padding="8">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Botón de Pin -->
|
||||
<Button Grid.Column="0"
|
||||
x:Name="PinButton"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Foreground="White"
|
||||
FontSize="16"
|
||||
Margin="0,0,10,0"
|
||||
ToolTip="Pin/Unpin aplicación actual"
|
||||
Click="PinButton_Click">
|
||||
<Button.Content>
|
||||
<TextBlock Text="{Binding IsPinnedMode, Converter={StaticResource BoolToPinConverter}}" />
|
||||
</Button.Content>
|
||||
<Button.Style>
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#4000BFFF"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
</Button>
|
||||
|
||||
<!-- Información de la aplicación -->
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<TextBlock Text="App:" FontWeight="Bold" Foreground="White" Margin="0,0,5,0"/>
|
||||
<TextBlock Text="{Binding CurrentApplication}" FontWeight="Bold" Foreground="Yellow"/>
|
||||
<TextBlock Text=" - " FontWeight="Bold" Foreground="White" Margin="5,0"/>
|
||||
<TextBlock Text="{Binding Shortcuts.Count, StringFormat=\{0\} shortcuts}" Foreground="LightGray"/>
|
||||
<TextBlock Text=" (Pinned)" FontWeight="Bold" Foreground="Orange" Margin="5,0,0,0"
|
||||
Visibility="{Binding IsPinnedMode, Converter={StaticResource BoolToVisibilityConverter}}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- DataGrid -->
|
||||
<DataGrid x:Name="ShortcutsDataGrid" Grid.Row="1"
|
||||
ItemsSource="{Binding Shortcuts}"
|
||||
SelectedItem="{Binding SelectedShortcut}"
|
||||
AutoGenerateColumns="False"
|
||||
CanUserAddRows="False"
|
||||
CanUserDeleteRows="False"
|
||||
SelectionMode="Single"
|
||||
GridLinesVisibility="Horizontal"
|
||||
HeadersVisibility="Column"
|
||||
AlternatingRowBackground="LightGray"
|
||||
RowHeaderWidth="0"
|
||||
CanUserSortColumns="False"
|
||||
IsReadOnly="False"
|
||||
MouseDoubleClick="ShortcutsDataGrid_MouseDoubleClick"
|
||||
CellEditEnding="ShortcutsDataGrid_CellEditEnding"
|
||||
RowEditEnding="ShortcutsDataGrid_RowEditEnding">
|
||||
<DataGrid.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="🗑️ Eliminar" Click="MenuItem_Delete_Click" />
|
||||
<Separator />
|
||||
<MenuItem Header="⌨️ Capturar Teclas" Click="MenuItem_CaptureKeys_Click" />
|
||||
</ContextMenu>
|
||||
</DataGrid.ContextMenu>
|
||||
<DataGrid.Columns>
|
||||
<DataGridCheckBoxColumn x:Name="FavoriteColumn"
|
||||
Header="⭐"
|
||||
Binding="{Binding IsFavorite, UpdateSourceTrigger=PropertyChanged}"
|
||||
Width="40"
|
||||
MinWidth="40"/>
|
||||
<DataGridTextColumn x:Name="ShortcutColumn"
|
||||
Header="Atajo"
|
||||
Binding="{Binding Shortcut, UpdateSourceTrigger=PropertyChanged}"
|
||||
Width="140"
|
||||
MinWidth="100"
|
||||
FontFamily="Consolas"
|
||||
FontWeight="Bold"/>
|
||||
<DataGridTemplateColumn x:Name="DescriptionColumn"
|
||||
Header="Descripción"
|
||||
Width="*"
|
||||
MinWidth="80">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Description}"
|
||||
TextWrapping="Wrap"
|
||||
MaxWidth="400"
|
||||
Margin="5,2"
|
||||
VerticalAlignment="Top"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate>
|
||||
<DataTemplate>
|
||||
<TextBox Text="{Binding Description, UpdateSourceTrigger=PropertyChanged}"
|
||||
TextWrapping="Wrap"
|
||||
AcceptsReturn="True"
|
||||
AcceptsTab="True"
|
||||
MinHeight="40"
|
||||
MaxHeight="120"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
BorderThickness="1"
|
||||
BorderBrush="LightBlue"
|
||||
Margin="1"
|
||||
Padding="2"
|
||||
VerticalAlignment="Stretch"
|
||||
VerticalContentAlignment="Top"
|
||||
HorizontalAlignment="Stretch"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellEditingTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
</DataGrid.Columns>
|
||||
<DataGrid.RowStyle>
|
||||
<Style TargetType="DataGridRow">
|
||||
<Setter Property="MinHeight" Value="40"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsFavorite}" Value="True">
|
||||
<Setter Property="Background" Value="LightYellow"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding IsModified}" Value="True">
|
||||
<Setter Property="FontStyle" Value="Italic"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</DataGrid.RowStyle>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
</Window>
|
|
@ -0,0 +1,268 @@
|
|||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using ShortcutsHelper.Models;
|
||||
using ShortcutsHelper.ViewModels;
|
||||
|
||||
namespace ShortcutsHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private MainViewModel ViewModel => (MainViewModel)DataContext;
|
||||
private bool _isInitialized = false;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.Closing += MainWindow_Closing;
|
||||
this.Loaded += MainWindow_Loaded;
|
||||
this.SizeChanged += MainWindow_SizeChanged;
|
||||
this.LocationChanged += MainWindow_LocationChanged;
|
||||
this.Deactivated += MainWindow_Deactivated;
|
||||
this.Activated += MainWindow_Activated;
|
||||
|
||||
// Suscribirse al evento de cambio de visibilidad
|
||||
ViewModel.VisibilityChanged += OnVisibilityChanged;
|
||||
}
|
||||
|
||||
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
LoadWindowSettings();
|
||||
this.Opacity = 1.0; // Inicializar con opacidad completa
|
||||
_isInitialized = true;
|
||||
}
|
||||
|
||||
private void OnVisibilityChanged(object? sender, bool shouldBeVisible)
|
||||
{
|
||||
// Ejecutar en el hilo de UI
|
||||
Dispatcher.BeginInvoke(() =>
|
||||
{
|
||||
if (shouldBeVisible)
|
||||
{
|
||||
// Mostrar ventana con opacidad completa
|
||||
if (this.Visibility == Visibility.Hidden)
|
||||
{
|
||||
this.Show();
|
||||
}
|
||||
if (this.WindowState == WindowState.Minimized)
|
||||
{
|
||||
this.WindowState = WindowState.Normal;
|
||||
}
|
||||
this.Opacity = 1.0; // 100% opacidad
|
||||
}
|
||||
else
|
||||
{
|
||||
// En lugar de ocultar, usar transparencia del 20%
|
||||
this.Opacity = 0.2; // 20% opacidad
|
||||
// Mantener la ventana visible pero semi-transparente
|
||||
if (this.Visibility == Visibility.Hidden)
|
||||
{
|
||||
this.Show();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void PinButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.TogglePinMode();
|
||||
}
|
||||
|
||||
private void LoadWindowSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
var settings = ViewModel.GetCurrentApplicationSettings();
|
||||
|
||||
this.Left = settings.Left;
|
||||
this.Top = settings.Top;
|
||||
this.Width = settings.Width;
|
||||
this.Height = settings.Height;
|
||||
|
||||
// Aplicar anchos de columna
|
||||
if (ShortcutsDataGrid != null)
|
||||
{
|
||||
FavoriteColumn.Width = settings.FavoriteColumnWidth;
|
||||
ShortcutColumn.Width = settings.ShortcutColumnWidth;
|
||||
// Para DataGridTemplateColumn necesitamos manejar el ancho diferente
|
||||
DescriptionColumn.Width = new DataGridLength(settings.DescriptionColumnWidth, DataGridLengthUnitType.Pixel);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error al cargar configuración de ventana: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void MainWindow_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
if (_isInitialized)
|
||||
{
|
||||
SaveWindowSettings();
|
||||
}
|
||||
}
|
||||
|
||||
private void MainWindow_LocationChanged(object? sender, EventArgs e)
|
||||
{
|
||||
if (_isInitialized)
|
||||
{
|
||||
SaveWindowSettings();
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveWindowSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
ViewModel.UpdateWindowSettings(this.Left, this.Top, this.Width, this.Height);
|
||||
|
||||
if (ShortcutsDataGrid != null)
|
||||
{
|
||||
// Para DataGridTemplateColumn obtenemos el ancho diferente
|
||||
double descriptionWidth = DescriptionColumn.ActualWidth;
|
||||
|
||||
ViewModel.UpdateColumnWidths(
|
||||
FavoriteColumn.Width.Value,
|
||||
ShortcutColumn.Width.Value,
|
||||
descriptionWidth);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error al guardar configuración de ventana: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void MainWindow_Deactivated(object? sender, EventArgs e)
|
||||
{
|
||||
// Confirmar cualquier edición pendiente en el DataGrid
|
||||
CommitDataGridEdits();
|
||||
|
||||
// Guardar cambios cuando pierde el foco
|
||||
ViewModel.SavePendingChanges();
|
||||
}
|
||||
|
||||
private void MainWindow_Activated(object? sender, EventArgs e)
|
||||
{
|
||||
// Cuando la ventana se activa, asegurar opacidad completa
|
||||
this.Opacity = 1.0;
|
||||
}
|
||||
|
||||
private void MainWindow_Closing(object? sender, System.ComponentModel.CancelEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Confirmar cualquier edición pendiente en el DataGrid
|
||||
CommitDataGridEdits();
|
||||
|
||||
SaveWindowSettings();
|
||||
|
||||
// Desuscribirse del evento
|
||||
ViewModel.VisibilityChanged -= OnVisibilityChanged;
|
||||
|
||||
ViewModel.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error al cerrar aplicación: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void MenuItem_Delete_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.DeleteSelectedShortcut();
|
||||
}
|
||||
|
||||
private void MenuItem_CaptureKeys_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.StartKeyCapture();
|
||||
}
|
||||
|
||||
private void ShortcutsDataGrid_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||
{
|
||||
// Verificar si se hizo doble clic en una celda (no en el header)
|
||||
if (e.OriginalSource is FrameworkElement element)
|
||||
{
|
||||
// Buscar si el clic fue en una celda del DataGrid
|
||||
var cell = element.GetVisualParent<DataGridCell>();
|
||||
if (cell != null && cell.Column == DescriptionColumn)
|
||||
{
|
||||
// Iniciar edición de la celda de descripción
|
||||
ShortcutsDataGrid.BeginEdit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ShortcutsDataGrid_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
|
||||
{
|
||||
// Este evento se dispara cuando termina la edición de una celda
|
||||
// No cancelamos la edición aquí, solo nos aseguramos de que se procese
|
||||
if (!e.Cancel)
|
||||
{
|
||||
// Forzar la actualización del binding
|
||||
Dispatcher.BeginInvoke(() =>
|
||||
{
|
||||
if (e.EditingElement is System.Windows.Controls.TextBox textBox)
|
||||
{
|
||||
var bindingExpression = textBox.GetBindingExpression(System.Windows.Controls.TextBox.TextProperty);
|
||||
bindingExpression?.UpdateSource();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void ShortcutsDataGrid_RowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
|
||||
{
|
||||
// Este evento se dispara cuando termina la edición de una fila
|
||||
// Aquí podrían guardarse los cambios si fuera necesario
|
||||
if (!e.Cancel && e.Row.Item is ShortcutRecord shortcut)
|
||||
{
|
||||
// Forzar guardar cambios después de que termine la edición de la fila
|
||||
Dispatcher.BeginInvoke(() =>
|
||||
{
|
||||
if (shortcut.IsModified)
|
||||
{
|
||||
ViewModel.SavePendingChanges();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void CommitDataGridEdits()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ShortcutsDataGrid != null)
|
||||
{
|
||||
// Terminar cualquier edición activa
|
||||
ShortcutsDataGrid.CommitEdit(DataGridEditingUnit.Row, true);
|
||||
ShortcutsDataGrid.CommitEdit(DataGridEditingUnit.Cell, true);
|
||||
|
||||
// Actualizar el binding
|
||||
ShortcutsDataGrid.UpdateLayout();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error al confirmar ediciones del DataGrid: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Método de extensión para buscar elementos padre en el árbol visual
|
||||
public static class VisualTreeHelperExtensions
|
||||
{
|
||||
public static T? GetVisualParent<T>(this DependencyObject child) where T : DependencyObject
|
||||
{
|
||||
var parent = VisualTreeHelper.GetParent(child);
|
||||
if (parent == null) return null;
|
||||
if (parent is T) return (T)parent;
|
||||
return parent.GetVisualParent<T>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace ShortcutsHelper.Models
|
||||
{
|
||||
public class AppConfiguration
|
||||
{
|
||||
public Dictionary<string, ApplicationSettings> Applications { get; set; } = new();
|
||||
public string LastActiveApplication { get; set; } = "";
|
||||
public int InactivityTimeoutMinutes { get; set; } = 5;
|
||||
public bool IsPinnedMode { get; set; } = true;
|
||||
public string PinnedApplication { get; set; } = "";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
using System.Text.Json;
|
||||
using System.IO;
|
||||
|
||||
namespace ShortcutsHelper.Models
|
||||
{
|
||||
public class AppSettings
|
||||
{
|
||||
public Dictionary<string, ApplicationWindowSettings> ApplicationSettings { get; set; } = new();
|
||||
}
|
||||
|
||||
public class ApplicationWindowSettings
|
||||
{
|
||||
public double Left { get; set; } = 100;
|
||||
public double Top { get; set; } = 100;
|
||||
public double Width { get; set; } = 400;
|
||||
public double Height { get; set; } = 300;
|
||||
public double FavoriteColumnWidth { get; set; } = 40;
|
||||
public double ShortcutColumnWidth { get; set; } = 140;
|
||||
public double DescriptionColumnWidth { get; set; } = 200;
|
||||
}
|
||||
|
||||
public static class SettingsManager
|
||||
{
|
||||
private static readonly string SettingsPath = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||
"ShortcutsHelper",
|
||||
"app-settings.json");
|
||||
|
||||
private static AppSettings? _settings;
|
||||
|
||||
public static AppSettings LoadSettings()
|
||||
{
|
||||
if (_settings != null)
|
||||
return _settings;
|
||||
|
||||
try
|
||||
{
|
||||
if (File.Exists(SettingsPath))
|
||||
{
|
||||
var json = File.ReadAllText(SettingsPath);
|
||||
_settings = JsonSerializer.Deserialize<AppSettings>(json) ?? new AppSettings();
|
||||
}
|
||||
else
|
||||
{
|
||||
_settings = new AppSettings();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
_settings = new AppSettings();
|
||||
}
|
||||
|
||||
return _settings;
|
||||
}
|
||||
|
||||
public static void SaveSettings(AppSettings settings)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dir = Path.GetDirectoryName(SettingsPath);
|
||||
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
|
||||
Directory.CreateDirectory(dir);
|
||||
|
||||
var json = JsonSerializer.Serialize(settings, new JsonSerializerOptions { WriteIndented = true });
|
||||
File.WriteAllText(SettingsPath, json);
|
||||
_settings = settings;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignorar errores al guardar
|
||||
}
|
||||
}
|
||||
|
||||
public static ApplicationWindowSettings GetApplicationSettings(string applicationName)
|
||||
{
|
||||
var settings = LoadSettings();
|
||||
|
||||
if (!settings.ApplicationSettings.ContainsKey(applicationName))
|
||||
{
|
||||
// Configuración por defecto para aplicaciones nuevas
|
||||
settings.ApplicationSettings[applicationName] = new ApplicationWindowSettings
|
||||
{
|
||||
Left = 100,
|
||||
Top = 100,
|
||||
Width = 400,
|
||||
Height = 300,
|
||||
FavoriteColumnWidth = 40,
|
||||
ShortcutColumnWidth = 140,
|
||||
DescriptionColumnWidth = 200
|
||||
};
|
||||
SaveSettings(settings);
|
||||
}
|
||||
|
||||
return settings.ApplicationSettings[applicationName];
|
||||
}
|
||||
|
||||
public static void SaveApplicationSettings(string applicationName, ApplicationWindowSettings appSettings)
|
||||
{
|
||||
var settings = LoadSettings();
|
||||
settings.ApplicationSettings[applicationName] = appSettings;
|
||||
SaveSettings(settings);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
namespace ShortcutsHelper.Models
|
||||
{
|
||||
public class ApplicationSettings
|
||||
{
|
||||
public double Left { get; set; } = 100;
|
||||
public double Top { get; set; } = 100;
|
||||
public double Width { get; set; } = 600;
|
||||
public double Height { get; set; } = 400;
|
||||
public double FavoriteColumnWidth { get; set; } = 40;
|
||||
public double ShortcutColumnWidth { get; set; } = 150;
|
||||
public double DescriptionColumnWidth { get; set; } = 350;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace ShortcutsHelper.Models
|
||||
{
|
||||
public class ShortcutRecord : INotifyPropertyChanged
|
||||
{
|
||||
private string _application = "";
|
||||
private string _shortcut = "";
|
||||
private string _description = "";
|
||||
private bool _isFavorite = false;
|
||||
private bool _isModified = false;
|
||||
|
||||
public string Application
|
||||
{
|
||||
get => _application;
|
||||
set
|
||||
{
|
||||
if (_application != value)
|
||||
{
|
||||
_application = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string Shortcut
|
||||
{
|
||||
get => _shortcut;
|
||||
set
|
||||
{
|
||||
if (_shortcut != value)
|
||||
{
|
||||
_shortcut = value;
|
||||
IsModified = true;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string Description
|
||||
{
|
||||
get => _description;
|
||||
set
|
||||
{
|
||||
if (_description != value)
|
||||
{
|
||||
_description = value;
|
||||
IsModified = true;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsFavorite
|
||||
{
|
||||
get => _isFavorite;
|
||||
set
|
||||
{
|
||||
if (_isFavorite != value)
|
||||
{
|
||||
_isFavorite = value;
|
||||
IsModified = true;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsModified
|
||||
{
|
||||
get => _isModified;
|
||||
set
|
||||
{
|
||||
if (_isModified != value)
|
||||
{
|
||||
_isModified = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using ShortcutsHelper.Models;
|
||||
|
||||
namespace ShortcutsHelper.Services
|
||||
{
|
||||
public class ConfigurationService
|
||||
{
|
||||
private const string ConfigFileName = "shortcuts_config.json";
|
||||
private static string ConfigFilePath => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "ShortcutsHelper", ConfigFileName);
|
||||
|
||||
public AppConfiguration LoadConfiguration()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(ConfigFilePath))
|
||||
{
|
||||
string json = File.ReadAllText(ConfigFilePath);
|
||||
return JsonSerializer.Deserialize<AppConfiguration>(json) ?? new AppConfiguration();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error al cargar configuración: {ex.Message}");
|
||||
}
|
||||
|
||||
return new AppConfiguration();
|
||||
}
|
||||
|
||||
public void SaveConfiguration(AppConfiguration config)
|
||||
{
|
||||
try
|
||||
{
|
||||
string directory = Path.GetDirectoryName(ConfigFilePath)!;
|
||||
if (!Directory.Exists(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
};
|
||||
|
||||
string json = JsonSerializer.Serialize(config, options);
|
||||
File.WriteAllText(ConfigFilePath, json);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error al guardar configuración: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public ApplicationSettings GetApplicationSettings(AppConfiguration config, string applicationName)
|
||||
{
|
||||
if (config.Applications.TryGetValue(applicationName, out var settings))
|
||||
{
|
||||
return settings;
|
||||
}
|
||||
|
||||
// Crear configuración por defecto para nueva aplicación
|
||||
var defaultSettings = new ApplicationSettings();
|
||||
config.Applications[applicationName] = defaultSettings;
|
||||
return defaultSettings;
|
||||
}
|
||||
|
||||
public void UpdateApplicationSettings(AppConfiguration config, string applicationName, ApplicationSettings settings)
|
||||
{
|
||||
config.Applications[applicationName] = settings;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using Gma.System.MouseKeyHook;
|
||||
|
||||
namespace ShortcutsHelper.Services
|
||||
{
|
||||
public class KeyCaptureService : IDisposable
|
||||
{
|
||||
private IKeyboardMouseEvents? _globalHook;
|
||||
private bool _isCapturing = false;
|
||||
private List<string> _capturedKeys = new();
|
||||
|
||||
public event EventHandler<string>? KeyCaptured;
|
||||
public event EventHandler? CaptureCancelled;
|
||||
|
||||
public bool IsCapturing => _isCapturing;
|
||||
|
||||
public void StartCapturing()
|
||||
{
|
||||
if (_isCapturing) return;
|
||||
|
||||
_isCapturing = true;
|
||||
_capturedKeys.Clear();
|
||||
|
||||
_globalHook = Hook.GlobalEvents();
|
||||
_globalHook.KeyDown += OnKeyDown;
|
||||
_globalHook.KeyUp += OnKeyUp;
|
||||
}
|
||||
|
||||
public void StopCapturing()
|
||||
{
|
||||
_isCapturing = false;
|
||||
if (_globalHook != null)
|
||||
{
|
||||
_globalHook.KeyDown -= OnKeyDown;
|
||||
_globalHook.KeyUp -= OnKeyUp;
|
||||
_globalHook.Dispose();
|
||||
_globalHook = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnKeyDown(object? sender, KeyEventArgs e)
|
||||
{
|
||||
if (!_isCapturing) return;
|
||||
|
||||
if (e.KeyCode == Keys.Escape)
|
||||
{
|
||||
StopCapturing();
|
||||
CaptureCancelled?.Invoke(this, EventArgs.Empty);
|
||||
return;
|
||||
}
|
||||
|
||||
var keyString = BuildKeyString(e);
|
||||
if (!string.IsNullOrEmpty(keyString))
|
||||
{
|
||||
_capturedKeys.Clear();
|
||||
_capturedKeys.Add(keyString);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnKeyUp(object? sender, KeyEventArgs e)
|
||||
{
|
||||
if (!_isCapturing || _capturedKeys.Count == 0) return;
|
||||
|
||||
StopCapturing();
|
||||
KeyCaptured?.Invoke(this, _capturedKeys.First().Replace(" ", ""));
|
||||
}
|
||||
|
||||
private string BuildKeyString(KeyEventArgs e)
|
||||
{
|
||||
var parts = new List<string>();
|
||||
|
||||
if ((Control.ModifierKeys & Keys.Control) != 0) parts.Add("Ctrl");
|
||||
if ((Control.ModifierKeys & Keys.Alt) != 0) parts.Add("Alt");
|
||||
if ((Control.ModifierKeys & Keys.Shift) != 0) parts.Add("Shift");
|
||||
if ((Control.ModifierKeys & Keys.LWin) != 0 || (Control.ModifierKeys & Keys.RWin) != 0) parts.Add("Win");
|
||||
|
||||
if (e.KeyCode != Keys.ControlKey && e.KeyCode != Keys.LControlKey && e.KeyCode != Keys.RControlKey &&
|
||||
e.KeyCode != Keys.Menu && e.KeyCode != Keys.LMenu && e.KeyCode != Keys.RMenu &&
|
||||
e.KeyCode != Keys.ShiftKey && e.KeyCode != Keys.LShiftKey && e.KeyCode != Keys.RShiftKey &&
|
||||
e.KeyCode != Keys.LWin && e.KeyCode != Keys.RWin)
|
||||
{
|
||||
parts.Add(GetKeyName(e.KeyCode));
|
||||
}
|
||||
|
||||
return string.Join("+", parts);
|
||||
}
|
||||
|
||||
private string GetKeyName(Keys key)
|
||||
{
|
||||
return key switch
|
||||
{
|
||||
Keys.Space => "Space",
|
||||
Keys.Enter => "Enter",
|
||||
Keys.Tab => "Tab",
|
||||
Keys.Back => "Backspace",
|
||||
Keys.Delete => "Delete",
|
||||
Keys.Insert => "Insert",
|
||||
Keys.Home => "Home",
|
||||
Keys.End => "End",
|
||||
Keys.PageUp => "PageUp",
|
||||
Keys.PageDown => "PageDown",
|
||||
Keys.Up => "↑",
|
||||
Keys.Down => "↓",
|
||||
Keys.Left => "←",
|
||||
Keys.Right => "→",
|
||||
Keys.F1 => "F1",
|
||||
Keys.F2 => "F2",
|
||||
Keys.F3 => "F3",
|
||||
Keys.F4 => "F4",
|
||||
Keys.F5 => "F5",
|
||||
Keys.F6 => "F6",
|
||||
Keys.F7 => "F7",
|
||||
Keys.F8 => "F8",
|
||||
Keys.F9 => "F9",
|
||||
Keys.F10 => "F10",
|
||||
Keys.F11 => "F11",
|
||||
Keys.F12 => "F12",
|
||||
_ => key.ToString()
|
||||
};
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
StopCapturing();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,232 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using libObsidean;
|
||||
using ShortcutsHelper.Models;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace ShortcutsHelper.Services
|
||||
{
|
||||
public class ShortcutService
|
||||
{
|
||||
private readonly Obsidean _obsidean = new();
|
||||
private readonly Dictionary<string, List<ShortcutRecord>> _shortcutsByApplication = new();
|
||||
private bool _isLoaded = false;
|
||||
|
||||
public event EventHandler<string>? ApplicationShortcutsChanged;
|
||||
|
||||
public void LoadAllShortcuts()
|
||||
{
|
||||
if (_isLoaded) return;
|
||||
|
||||
try
|
||||
{
|
||||
var tabla = _obsidean.LeerShortcuts();
|
||||
if (tabla != null && tabla.GetLength(0) > 1)
|
||||
{
|
||||
_shortcutsByApplication.Clear();
|
||||
|
||||
for (int i = 1; i < tabla.GetLength(0); i++)
|
||||
{
|
||||
string app = tabla[i, 0] ?? "";
|
||||
string shortcut = tabla[i, 1] ?? "";
|
||||
string description = tabla[i, 2] ?? "";
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(app) && (!string.IsNullOrWhiteSpace(shortcut) || !string.IsNullOrWhiteSpace(description)))
|
||||
{
|
||||
bool isFavorite = false;
|
||||
if (tabla.GetLength(1) > 3 && !string.IsNullOrWhiteSpace(tabla[i, 3]))
|
||||
{
|
||||
bool.TryParse(tabla[i, 3], out isFavorite);
|
||||
}
|
||||
|
||||
var record = new ShortcutRecord
|
||||
{
|
||||
Application = app,
|
||||
Shortcut = shortcut,
|
||||
Description = description,
|
||||
IsFavorite = isFavorite,
|
||||
IsModified = false
|
||||
};
|
||||
|
||||
if (!_shortcutsByApplication.ContainsKey(app))
|
||||
{
|
||||
_shortcutsByApplication[app] = new List<ShortcutRecord>();
|
||||
}
|
||||
|
||||
_shortcutsByApplication[app].Add(record);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_isLoaded = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error al cargar shortcuts: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public List<ShortcutRecord> GetShortcutsForApplication(string applicationName)
|
||||
{
|
||||
if (!_isLoaded) LoadAllShortcuts();
|
||||
|
||||
if (_shortcutsByApplication.TryGetValue(applicationName, out var shortcuts))
|
||||
{
|
||||
return shortcuts.OrderByDescending(s => s.IsFavorite).ThenBy(s => s.Shortcut).ToList();
|
||||
}
|
||||
|
||||
return new List<ShortcutRecord>();
|
||||
}
|
||||
|
||||
public void AddOrUpdateShortcut(ShortcutRecord shortcut)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(shortcut.Application)) return;
|
||||
|
||||
if (!_shortcutsByApplication.ContainsKey(shortcut.Application))
|
||||
{
|
||||
_shortcutsByApplication[shortcut.Application] = new List<ShortcutRecord>();
|
||||
}
|
||||
|
||||
var existing = _shortcutsByApplication[shortcut.Application]
|
||||
.FirstOrDefault(s => s.Shortcut == shortcut.Shortcut);
|
||||
|
||||
if (existing != null)
|
||||
{
|
||||
existing.Description = shortcut.Description;
|
||||
existing.IsFavorite = shortcut.IsFavorite;
|
||||
// No marcar como IsModified = false aquí, dejar que SaveAllShortcuts() lo haga
|
||||
}
|
||||
else
|
||||
{
|
||||
// No marcar como IsModified = false aquí, dejar que SaveAllShortcuts() lo haga
|
||||
_shortcutsByApplication[shortcut.Application].Add(shortcut);
|
||||
}
|
||||
|
||||
ApplicationShortcutsChanged?.Invoke(this, shortcut.Application);
|
||||
}
|
||||
|
||||
public void RemoveShortcut(string applicationName, string shortcut)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(applicationName) || string.IsNullOrWhiteSpace(shortcut)) return;
|
||||
|
||||
if (_shortcutsByApplication.TryGetValue(applicationName, out var shortcuts))
|
||||
{
|
||||
var toRemove = shortcuts.FirstOrDefault(s => s.Shortcut == shortcut);
|
||||
if (toRemove != null)
|
||||
{
|
||||
shortcuts.Remove(toRemove);
|
||||
ApplicationShortcutsChanged?.Invoke(this, applicationName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveAllShortcuts()
|
||||
{
|
||||
try
|
||||
{
|
||||
var allShortcuts = new List<ShortcutRecord>();
|
||||
foreach (var appShortcuts in _shortcutsByApplication.Values)
|
||||
{
|
||||
allShortcuts.AddRange(appShortcuts.Where(s => !string.IsNullOrWhiteSpace(s.Shortcut)));
|
||||
}
|
||||
|
||||
if (allShortcuts.Count == 0) return;
|
||||
|
||||
// Crear tabla para guardar
|
||||
var tabla = new string[allShortcuts.Count + 1, 4];
|
||||
tabla[0, 0] = "Application";
|
||||
tabla[0, 1] = "Shortcut";
|
||||
tabla[0, 2] = "Description";
|
||||
tabla[0, 3] = "IsFavorite";
|
||||
|
||||
for (int i = 0; i < allShortcuts.Count; i++)
|
||||
{
|
||||
var shortcut = allShortcuts[i];
|
||||
tabla[i + 1, 0] = shortcut.Application;
|
||||
tabla[i + 1, 1] = shortcut.Shortcut;
|
||||
tabla[i + 1, 2] = shortcut.Description;
|
||||
tabla[i + 1, 3] = shortcut.IsFavorite.ToString();
|
||||
}
|
||||
|
||||
// Guardar en Obsidian
|
||||
var vaultPath = GetVaultPath("VM");
|
||||
if (!string.IsNullOrEmpty(vaultPath))
|
||||
{
|
||||
string pathToMarkdown = Path.Combine(vaultPath, "DB", "Shortcuts", "Shortcuts.md");
|
||||
libObsidean.Obsidean.SaveTableToMarkdown(pathToMarkdown, tabla);
|
||||
|
||||
// Marcar todos como no modificados
|
||||
foreach (var appShortcuts in _shortcutsByApplication.Values)
|
||||
{
|
||||
foreach (var shortcut in appShortcuts)
|
||||
{
|
||||
shortcut.IsModified = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error al guardar shortcuts: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasModifiedShortcuts()
|
||||
{
|
||||
return _shortcutsByApplication.Values
|
||||
.Any(shortcuts => shortcuts.Any(s => s.IsModified));
|
||||
}
|
||||
|
||||
public List<string> GetAllApplicationNames()
|
||||
{
|
||||
if (!_isLoaded) LoadAllShortcuts();
|
||||
return _shortcutsByApplication.Keys.OrderBy(k => k).ToList();
|
||||
}
|
||||
|
||||
private string? GetVaultPath(string vaultName)
|
||||
{
|
||||
try
|
||||
{
|
||||
string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
string pathToJsonFile = Path.Combine(appDataPath, "obsidian", "obsidian.json");
|
||||
if (File.Exists(pathToJsonFile))
|
||||
{
|
||||
string jsonContent = File.ReadAllText(pathToJsonFile);
|
||||
var jsonObject = JObject.Parse(jsonContent);
|
||||
var vaults = jsonObject["vaults"] as JObject;
|
||||
|
||||
if (vaults != null)
|
||||
{
|
||||
foreach (var vault in vaults)
|
||||
{
|
||||
var pathToken = vault.Value?["path"];
|
||||
if (pathToken != null)
|
||||
{
|
||||
string? path = pathToken.ToString();
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
string? dirPath = Path.GetDirectoryName(path.TrimEnd('\\') + "\\");
|
||||
if (!string.IsNullOrEmpty(dirPath))
|
||||
{
|
||||
string lastDirectoryName = Path.GetFileName(dirPath);
|
||||
if (lastDirectoryName.Equals(vaultName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("Error al leer vault: " + ex.Message);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
},
|
||||
{
|
||||
"path": "../Libraries/libObsidean"
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"dotnet.defaultSolution": "ShortcutsHelper/ShortcutsHelper.sln"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UseWPF>true</UseWPF>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
|
||||
<PackageReference Include="MouseKeyHook" Version="5.7.1" />
|
||||
<PackageReference Include="Extended.Wpf.Toolkit" Version="4.6.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Libraries\libObsidean\libObsidean.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Resource Include="C:\Trabajo\Graphics\Icons\close.png" Link="Icons\close.png" />
|
||||
<Resource Include="C:\Trabajo\Graphics\Icons\delete.png" Link="Icons\delete.png" />
|
||||
<Resource Include="C:\Trabajo\Graphics\Icons\add-button.png" Link="Icons\add-button.png" />
|
||||
<Resource Include="C:\Trabajo\Graphics\Icons\remove.png" Link="Icons\remove.png" />
|
||||
<Resource Include="C:\Trabajo\Graphics\Icons\use.png" Link="Icons\use.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Icons\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.13.35931.197 d17.13
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShortcutsHelper", "ShortcutsHelper.csproj", "{B3511550-5621-491D-A554-412664C38E4B}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{B3511550-5621-491D-A554-412664C38E4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B3511550-5621-491D-A554-412664C38E4B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B3511550-5621-491D-A554-412664C38E4B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B3511550-5621-491D-A554-412664C38E4B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {17860D8C-E283-4288-89AA-CC11A43770EC}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -0,0 +1,412 @@
|
|||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using ShortcutsHelper.Models;
|
||||
using ShortcutsHelper.Services;
|
||||
using WpfApplication = System.Windows.Application;
|
||||
using SystemTimer = System.Timers.Timer;
|
||||
|
||||
namespace ShortcutsHelper.ViewModels
|
||||
{
|
||||
public class MainViewModel : INotifyPropertyChanged, IDisposable
|
||||
{
|
||||
private readonly ShortcutService _shortcutService;
|
||||
private readonly ConfigurationService _configService;
|
||||
private readonly KeyCaptureService _keyCaptureService;
|
||||
private readonly SystemTimer _appDetectionTimer;
|
||||
private readonly SystemTimer _inactivityTimer;
|
||||
|
||||
private string _currentApplication = "";
|
||||
private ShortcutRecord? _selectedShortcut;
|
||||
private string _originalDescription = "";
|
||||
private bool _isInitialized = false;
|
||||
private bool _isPinnedMode = true; // Por defecto está activo
|
||||
|
||||
public ObservableCollection<ShortcutRecord> Shortcuts { get; } = new();
|
||||
|
||||
public string CurrentApplication
|
||||
{
|
||||
get => _currentApplication;
|
||||
private set
|
||||
{
|
||||
if (_currentApplication != value)
|
||||
{
|
||||
_currentApplication = value;
|
||||
OnPropertyChanged();
|
||||
LoadShortcutsForCurrentApp();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ShortcutRecord? SelectedShortcut
|
||||
{
|
||||
get => _selectedShortcut;
|
||||
set
|
||||
{
|
||||
if (_selectedShortcut != value)
|
||||
{
|
||||
_selectedShortcut = value;
|
||||
OnPropertyChanged();
|
||||
EnsureEmptyRows();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsCapturingKeys => _keyCaptureService.IsCapturing;
|
||||
|
||||
public bool IsPinnedMode
|
||||
{
|
||||
get => _isPinnedMode;
|
||||
set
|
||||
{
|
||||
if (_isPinnedMode != value)
|
||||
{
|
||||
_isPinnedMode = value;
|
||||
OnPropertyChanged();
|
||||
|
||||
// Actualizar configuración
|
||||
_appConfig.IsPinnedMode = value;
|
||||
|
||||
if (value)
|
||||
{
|
||||
// Al activar Pin, guardar la aplicación actual como pinned (si no está vacía)
|
||||
if (!string.IsNullOrEmpty(CurrentApplication))
|
||||
{
|
||||
_appConfig.PinnedApplication = CurrentApplication;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Al desactivar Pin, limpiar la aplicación pinned y mostrar la ventana
|
||||
_appConfig.PinnedApplication = "";
|
||||
VisibilityChanged?.Invoke(this, true); // Mostrar ventana cuando se desactiva Pin
|
||||
}
|
||||
|
||||
_configService.SaveConfiguration(_appConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Configuración de aplicación actual
|
||||
private AppConfiguration _appConfig = new();
|
||||
private ApplicationSettings? _currentAppSettings;
|
||||
|
||||
// Evento para notificar cambios de visibilidad de la ventana
|
||||
public event EventHandler<bool>? VisibilityChanged;
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
public MainViewModel()
|
||||
{
|
||||
_shortcutService = new ShortcutService();
|
||||
_configService = new ConfigurationService();
|
||||
_keyCaptureService = new KeyCaptureService();
|
||||
|
||||
// Configurar timers
|
||||
_appDetectionTimer = new SystemTimer(1000); // Cada segundo
|
||||
_appDetectionTimer.Elapsed += OnAppDetectionTimer;
|
||||
|
||||
_inactivityTimer = new SystemTimer(TimeSpan.FromMinutes(5).TotalMilliseconds); // 5 minutos por defecto
|
||||
_inactivityTimer.Elapsed += OnInactivityTimer;
|
||||
|
||||
// Suscribirse a eventos
|
||||
_keyCaptureService.KeyCaptured += OnKeyCaptured;
|
||||
_keyCaptureService.CaptureCancelled += OnCaptureCancelled;
|
||||
_shortcutService.ApplicationShortcutsChanged += OnApplicationShortcutsChanged;
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
if (_isInitialized) return;
|
||||
|
||||
// Cargar configuración
|
||||
_appConfig = _configService.LoadConfiguration();
|
||||
_inactivityTimer.Interval = TimeSpan.FromMinutes(_appConfig.InactivityTimeoutMinutes).TotalMilliseconds;
|
||||
|
||||
// Restaurar estado de Pin
|
||||
_isPinnedMode = _appConfig.IsPinnedMode;
|
||||
OnPropertyChanged(nameof(IsPinnedMode));
|
||||
|
||||
// Cargar todos los shortcuts de una vez
|
||||
_shortcutService.LoadAllShortcuts();
|
||||
|
||||
// Si está en modo Pin y no hay aplicación pinned, establecer la primera aplicación detectada
|
||||
// Pero primero detectar la aplicación actual para establecer CurrentApplication
|
||||
string initialApp = GetActiveProcessName();
|
||||
if (!string.IsNullOrWhiteSpace(initialApp) &&
|
||||
!initialApp.Equals("ShortcutsHelper", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
CurrentApplication = initialApp;
|
||||
LoadApplicationSettings();
|
||||
|
||||
// Si está en modo Pin y no hay aplicación pinned, establecer esta como pinned
|
||||
if (IsPinnedMode && string.IsNullOrEmpty(_appConfig.PinnedApplication))
|
||||
{
|
||||
_appConfig.PinnedApplication = initialApp;
|
||||
_configService.SaveConfiguration(_appConfig);
|
||||
}
|
||||
}
|
||||
|
||||
// Iniciar detección de aplicación
|
||||
_appDetectionTimer.Start();
|
||||
|
||||
// Iniciar timer de inactividad
|
||||
_inactivityTimer.Start();
|
||||
|
||||
_isInitialized = true;
|
||||
}
|
||||
|
||||
private void OnAppDetectionTimer(object? sender, System.Timers.ElapsedEventArgs e)
|
||||
{
|
||||
DetectActiveApplication();
|
||||
}
|
||||
|
||||
private void OnInactivityTimer(object? sender, System.Timers.ElapsedEventArgs e)
|
||||
{
|
||||
// Guardar cambios por inactividad
|
||||
SavePendingChanges();
|
||||
}
|
||||
|
||||
private void DetectActiveApplication()
|
||||
{
|
||||
string appName = GetActiveProcessName();
|
||||
if (string.IsNullOrWhiteSpace(appName))
|
||||
return;
|
||||
|
||||
// Si está en modo Pin
|
||||
if (IsPinnedMode)
|
||||
{
|
||||
// Si no hay aplicación pinned, establecer la aplicación actual (excepto ShortcutsHelper)
|
||||
if (string.IsNullOrEmpty(_appConfig.PinnedApplication) &&
|
||||
!appName.Equals("ShortcutsHelper", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_appConfig.PinnedApplication = appName;
|
||||
CurrentApplication = appName;
|
||||
LoadApplicationSettings();
|
||||
_configService.SaveConfiguration(_appConfig);
|
||||
return;
|
||||
}
|
||||
|
||||
// Si hay aplicación pinned, manejar visibilidad
|
||||
if (!string.IsNullOrEmpty(_appConfig.PinnedApplication))
|
||||
{
|
||||
bool isShortcutsHelper = appName.Equals("ShortcutsHelper", StringComparison.OrdinalIgnoreCase);
|
||||
bool isPinnedApp = appName.Equals(_appConfig.PinnedApplication, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
// Mostrar ventana solo si está en ShortcutsHelper o en la aplicación pinned
|
||||
bool shouldBeVisible = isShortcutsHelper || isPinnedApp;
|
||||
VisibilityChanged?.Invoke(this, shouldBeVisible);
|
||||
|
||||
// No cambiar la aplicación actual cuando está pinned
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Modo normal: cambiar aplicación pero no mostrar ShortcutsHelper
|
||||
if (appName.Equals("ShortcutsHelper", StringComparison.OrdinalIgnoreCase))
|
||||
return;
|
||||
|
||||
if (appName != CurrentApplication)
|
||||
{
|
||||
SaveCurrentApplicationSettings();
|
||||
CurrentApplication = appName;
|
||||
LoadApplicationSettings();
|
||||
}
|
||||
}
|
||||
|
||||
public void TogglePinMode()
|
||||
{
|
||||
IsPinnedMode = !IsPinnedMode;
|
||||
}
|
||||
|
||||
private void LoadShortcutsForCurrentApp()
|
||||
{
|
||||
WpfApplication.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
Shortcuts.Clear();
|
||||
var shortcuts = _shortcutService.GetShortcutsForApplication(CurrentApplication);
|
||||
|
||||
foreach (var shortcut in shortcuts)
|
||||
{
|
||||
Shortcuts.Add(shortcut);
|
||||
}
|
||||
|
||||
EnsureEmptyRows();
|
||||
});
|
||||
}
|
||||
|
||||
private void EnsureEmptyRows()
|
||||
{
|
||||
WpfApplication.Current.Dispatcher.BeginInvoke(() =>
|
||||
{
|
||||
var emptyRows = Shortcuts.Where(s => string.IsNullOrWhiteSpace(s.Shortcut) && string.IsNullOrWhiteSpace(s.Description)).Count();
|
||||
while (emptyRows < 3)
|
||||
{
|
||||
Shortcuts.Add(new ShortcutRecord { Application = CurrentApplication });
|
||||
emptyRows++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void OnApplicationShortcutsChanged(object? sender, string applicationName)
|
||||
{
|
||||
if (applicationName == CurrentApplication)
|
||||
{
|
||||
LoadShortcutsForCurrentApp();
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteSelectedShortcut()
|
||||
{
|
||||
if (SelectedShortcut != null && !string.IsNullOrWhiteSpace(SelectedShortcut.Shortcut))
|
||||
{
|
||||
_shortcutService.RemoveShortcut(CurrentApplication, SelectedShortcut.Shortcut);
|
||||
Shortcuts.Remove(SelectedShortcut);
|
||||
EnsureEmptyRows();
|
||||
}
|
||||
}
|
||||
|
||||
public void StartKeyCapture()
|
||||
{
|
||||
if (SelectedShortcut == null) return;
|
||||
|
||||
if (_keyCaptureService.IsCapturing)
|
||||
{
|
||||
_keyCaptureService.StopCapturing();
|
||||
return;
|
||||
}
|
||||
|
||||
_originalDescription = SelectedShortcut.Description;
|
||||
SelectedShortcut.Description = "Presiona las teclas... (Esc para cancelar)";
|
||||
_keyCaptureService.StartCapturing();
|
||||
OnPropertyChanged(nameof(IsCapturingKeys));
|
||||
}
|
||||
|
||||
private void OnKeyCaptured(object? sender, string keyString)
|
||||
{
|
||||
if (SelectedShortcut != null)
|
||||
{
|
||||
SelectedShortcut.Shortcut = keyString;
|
||||
SelectedShortcut.Description = _originalDescription;
|
||||
SelectedShortcut.IsModified = true;
|
||||
}
|
||||
OnPropertyChanged(nameof(IsCapturingKeys));
|
||||
}
|
||||
|
||||
private void OnCaptureCancelled(object? sender, EventArgs e)
|
||||
{
|
||||
if (SelectedShortcut != null)
|
||||
{
|
||||
SelectedShortcut.Description = _originalDescription;
|
||||
}
|
||||
OnPropertyChanged(nameof(IsCapturingKeys));
|
||||
}
|
||||
|
||||
public void SavePendingChanges()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Guardar shortcuts modificados
|
||||
var modifiedShortcuts = Shortcuts.Where(s => s.IsModified && !string.IsNullOrWhiteSpace(s.Shortcut)).ToList();
|
||||
foreach (var shortcut in modifiedShortcuts)
|
||||
{
|
||||
_shortcutService.AddOrUpdateShortcut(shortcut);
|
||||
}
|
||||
|
||||
if (_shortcutService.HasModifiedShortcuts())
|
||||
{
|
||||
_shortcutService.SaveAllShortcuts();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error al guardar cambios: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public ApplicationSettings GetCurrentApplicationSettings()
|
||||
{
|
||||
return _configService.GetApplicationSettings(_appConfig, CurrentApplication);
|
||||
}
|
||||
|
||||
public void SaveCurrentApplicationSettings()
|
||||
{
|
||||
if (string.IsNullOrEmpty(CurrentApplication) || _currentAppSettings == null) return;
|
||||
|
||||
_configService.UpdateApplicationSettings(_appConfig, CurrentApplication, _currentAppSettings);
|
||||
_configService.SaveConfiguration(_appConfig);
|
||||
}
|
||||
|
||||
private void LoadApplicationSettings()
|
||||
{
|
||||
_currentAppSettings = _configService.GetApplicationSettings(_appConfig, CurrentApplication);
|
||||
}
|
||||
|
||||
public void UpdateWindowSettings(double left, double top, double width, double height)
|
||||
{
|
||||
if (_currentAppSettings != null)
|
||||
{
|
||||
_currentAppSettings.Left = left;
|
||||
_currentAppSettings.Top = top;
|
||||
_currentAppSettings.Width = width;
|
||||
_currentAppSettings.Height = height;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateColumnWidths(double favoriteWidth, double shortcutWidth, double descriptionWidth)
|
||||
{
|
||||
if (_currentAppSettings != null)
|
||||
{
|
||||
_currentAppSettings.FavoriteColumnWidth = favoriteWidth;
|
||||
_currentAppSettings.ShortcutColumnWidth = shortcutWidth;
|
||||
_currentAppSettings.DescriptionColumnWidth = descriptionWidth;
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetActiveProcessName()
|
||||
{
|
||||
IntPtr hWnd = GetForegroundWindow();
|
||||
if (hWnd == IntPtr.Zero) return string.Empty;
|
||||
|
||||
GetWindowThreadProcessId(hWnd, out uint pid);
|
||||
try
|
||||
{
|
||||
var proc = Process.GetProcessById((int)pid);
|
||||
return proc.ProcessName;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
SavePendingChanges();
|
||||
SaveCurrentApplicationSettings();
|
||||
|
||||
_appDetectionTimer?.Stop();
|
||||
_appDetectionTimer?.Dispose();
|
||||
_inactivityTimer?.Stop();
|
||||
_inactivityTimer?.Dispose();
|
||||
_keyCaptureService?.Dispose();
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr GetForegroundWindow();
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue