Este artículo detalla la implementación de un reloj de escritorio personalizable en C# con Windows Forms. La aplicación incluye características como una ventana transparente, la capacidad de establecer recordatorios (alarmas) y la opción de iniciarse automáticamente con el sistema operativo. El objetivo es proporcionar un ejemplo funcional y bien estructurado para desarrolladores de aplicaciones de escritorio.
Organización del Proyecto
La estructura del proyecto sigue una organización modular para separar las responsabilidades de la interfaz de usuario, la lógica de negocio y las utilidades del sistema.
RelojEscritorio/
├── Propiedades/
│ ├── AssemblyInfo.cs
│ └── Resources.resx
├── Vistas/
│ ├── VentanaPrincipal.cs
│ ├── VentanaPrincipal.Designer.cs
│ ├── FormularioAlarma.cs
│ └── FormularioListaAlarmas.cs
├── Modelos/
│ ├── GestorAlarmas.cs
│ └── PosicionVentana.cs
├── Utilidades/
│ ├── UtilidadesRegistro.cs
│ └── Win32API.cs (si se usa, aunque no se detalla en el original proporcionado)
├── Iconos/
│ ├── reloj.ico
│ └── alarma.ico
└── InicioAplicacion.cs
Implementación del Código
1. Punto de Entrada Principal (InicioAplicacion.cs)
Este módulo es el punto de entrada de la aplicación, encargado de la inicialización y de asegurar que solo se ejecute una instancia del reloj.
using System;
using System.Threading;
using System.Windows.Forms;
using RelojEscritorio.Configuracion; // Nuevo namespace para Settings
namespace RelojEscritorio
{
internal static class InicioAplicacion
{
/// <summary>
/// Punto de entrada principal para la aplicación.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
// Asegura una única instancia de la aplicación
bool nuevaInstancia;
using (var mutexApp = new Mutex(true, "RelojEscritorio_MutexUnico", out nuevaInstancia))
{
if (nuevaInstancia)
{
CargarAjustesIniciales();
Application.Run(new VentanaPrincipal());
}
else
{
MessageBox.Show("El Reloj de Escritorio ya está en ejecución.", "Información",
MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
}
/// <summary<
/// Carga los ajustes de usuario al iniciar la aplicación.
/// </summary>
private static void CargarAjustesIniciales()
{
if (AjustesAplicacion.Default.ActualizacionRequerida)
{
AjustesAplicacion.Default.Upgrade();
AjustesAplicacion.Default.ActualizacionRequerida = false;
AjustesAplicacion.Default.Save();
}
}
}
}
2. Ventana Principal (VentanaPrincipal.cs)
La ventana principal es el corazón visual del reloj, manejando la visualización, la interacicón del usuario y la gestión de las alarmas y ajustes.
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
using System.Media;
using RelojEscritorio.Modelos; // Nuevo namespace para Alarm, AlarmManager
using RelojEscritorio.Configuracion; // Nuevo namespace para Settings
using RelojEscritorio.Utilidades; // Nuevo namespace para RegistryHelper
using RelojEscritorio.Propiedades; // Nuevo namespace para Resources
namespace RelojEscritorio.Vistas
{
public partial class VentanaPrincipal : Form
{
// Campos privados para el estado del formulario y la lógica
private bool estaArrastrando = false;
private Point puntoInicioArrastre;
private GestorAlarmas gestorAlarmas;
private System.Windows.Forms.Timer temporizadorActualizacion;
private System.Windows.Forms.Timer temporizadorSegundos;
private NotifyIcon iconoBandejaSistema;
private ContextMenuStrip menuContextualBandeja;
// Niveles de transparencia disponibles
private float[] nivelesOpacidad = { 0.3f, 0.5f, 0.7f, 0.9f, 1.0f };
private int indiceOpacidadActual = 3; // Por defecto: 0.9 (90%)
// Parámetros de estilo del reloj
private EstiloVisualReloj estiloVisualActual = EstiloVisualReloj.Digital;
private Font fuenteReloj;
private Color colorReloj = Color.Lime;
private Color colorSombra = Color.Black;
private bool mostrarSombra = true;
private bool mostrarSegundos = true;
private bool mostrarFecha = true;
private bool mostrarDiaSemana = true;
// Posición y dimensiones del reloj
private PosicionVentana posicionGuardada = new PosicionVentana();
public VentanaPrincipal()
{
InitializeComponent();
ConfigurarComponentesIniciales();
CargarAjustes();
InicializarIconoBandeja();
IniciarRelojes(); // Arranca los temporizadores
}
/// <summary>
/// Configura las propiedades iniciales del formulario y los temporizadores.
/// </summary>
private void ConfigurarComponentesIniciales()
{
this.FormBorderStyle = FormBorderStyle.None;
this.BackColor = Color.Black;
this.TransparencyKey = Color.Black;
this.TopMost = true;
this.ShowInTaskbar = false;
this.StartPosition = FormStartPosition.Manual;
this.ClientSize = new Size(300, 150); // Tamaño por defecto
// Temporizador para actualizaciones generales y alarmas (cada segundo)
temporizadorActualizacion = new System.Windows.Forms.Timer { Interval = 1000 };
temporizadorActualizacion.Tick += EnActualizacionTemporizador;
// Temporizador para animaciones de segundos (cada 100 ms)
temporizadorSegundos = new System.Windows.Forms.Timer { Interval = 100 };
temporizadorSegundos.Tick += EnTemporizadorSegundos;
// Inicializa el gestor de alarmas
gestorAlarmas = new GestorAlarmas();
gestorAlarmas.AlarmaActivada += EnAlarmaActivada;
}
/// <summary>
/// Carga los ajustes de la aplicación persistidos.
/// </summary>
private void CargarAjustes()
{
// Posición y tamaño
posicionGuardada = AjustesAplicacion.Default.PosicionVentanaPrincipal;
if (posicionGuardada != null && posicionGuardada.EsValida())
{
this.Location = posicionGuardada.Ubicacion;
this.Size = posicionGuardada.Dimensiones;
}
else
{
// Posición por defecto: esquina superior derecha
this.Location = new Point(
Screen.PrimaryScreen.WorkingArea.Right - this.Width - 20, 20
);
}
// Opacidad
indiceOpacidadActual = AjustesAplicacion.Default.NivelOpacidad;
if (indiceOpacidadActual >= 0 && indiceOpacidadActual < nivelesOpacidad.Length)
{
this.Opacity = nivelesOpacidad[indiceOpacidadActual];
}
else
{
this.Opacity = 0.9f; // Valor por defecto si falla
}
// Estilo del reloj y visualización de elementos
estiloVisualActual = (EstiloVisualReloj)AjustesAplicacion.Default.EstiloRelojSeleccionado;
mostrarSegundos = AjustesAplicacion.Default.MostrarSegundos;
mostrarFecha = AjustesAplicacion.Default.MostrarFecha;
mostrarDiaSemana = AjustesAplicacion.Default.MostrarDiaSemana;
colorReloj = AjustesAplicacion.Default.ColorPrincipalReloj;
// Fuente
try
{
fuenteReloj = new Font(AjustesAplicacion.Default.NombreFuente, AjustesAplicacion.Default.TamañoFuente);
}
catch
{
fuenteReloj = new Font("Arial", 24); // Fuente por defecto
}
// Ajustes de autoarranque
ActualizarElementoMenuInicioAutomatico();
// Cargar alarmas
gestorAlarmas.DeserializarAlarmasDesdeXml(AjustesAplicacion.Default.AlarmasSerializadas);
}
/// <summary>
/// Guarda los ajustes actuales de la aplicación.
/// </summary>
private void GuardarAjustes()
{
AjustesAplicacion.Default.PosicionVentanaPrincipal = new PosicionVentana(this.Location, this.Size);
AjustesAplicacion.Default.NivelOpacidad = indiceOpacidadActual;
AjustesAplicacion.Default.EstiloRelojSeleccionado = (int)estiloVisualActual;
AjustesAplicacion.Default.MostrarSegundos = mostrarSegundos;
AjustesAplicacion.Default.MostrarFecha = mostrarFecha;
AjustesAplicacion.Default.MostrarDiaSemana = mostrarDiaSemana;
AjustesAplicacion.Default.ColorPrincipalReloj = colorReloj;
AjustesAplicacion.Default.NombreFuente = fuenteReloj.Name;
AjustesAplicacion.Default.TamañoFuente = fuenteReloj.Size;
AjustesAplicacion.Default.Save();
}
/// <summary>
/// Configura el icono de la bandeja del sistema y su menú contextual.
/// </summary>
private void InicializarIconoBandeja()
{
iconoBandejaSistema = new NotifyIcon();
iconoBandejaSistema.Icon = Resources.reloj_icono; // Carga desde recursos
iconoBandejaSistema.Text = "Reloj de Escritorio C#";
iconoBandejaSistema.Visible = true;
iconoBandejaSistema.MouseClick += EnClickIconoBandeja;
menuContextualBandeja = new ContextMenuStrip();
// Opciones de estilo de reloj
var menuEstilos = new ToolStripMenuItem("Estilo del Reloj");
menuEstilos.DropDownItems.Add("Digital", null, (s, e) => CambiarEstiloReloj(EstiloVisualReloj.Digital));
menuEstilos.DropDownItems.Add("Analógico", null, (s, e) => CambiarEstiloReloj(EstiloVisualReloj.Analogico));
menuEstilos.DropDownItems.Add("Minimalista", null, (s, e) => CambiarEstiloReloj(EstiloVisualReloj.Minimalista));
menuContextualBandeja.Items.Add(menuEstilos);
menuContextualBandeja.Items.Add(new ToolStripSeparator());
menuContextualBandeja.Items.Add("Ajustar Opacidad", null, (s, e) => CambiarOpacidad());
menuContextualBandeja.Items.Add("Seleccionar Color...", null, (s, e) => CambiarColor());
menuContextualBandeja.Items.Add(new ToolStripSeparator());
menuContextualBandeja.Items.Add("Mostrar/Ocultar Segundos", null, (s, e) => AlternarMostrarSegundos());
menuContextualBandeja.Items.Add("Mostrar/Ocultar Fecha", null, (s, e) => AlternarMostrarFecha());
menuContextualBandeja.Items.Add("Mostrar/Ocultar Día de la Semana", null, (s, e) => AlternarMostrarDiaSemana());
menuContextualBandeja.Items.Add(new ToolStripSeparator());
// Gestión de alarmas
var menuAlarmas = new ToolStripMenuItem("Gestión de Alarmas");
menuAlarmas.DropDownItems.Add("Añadir Alarma...", null, (s, e) => AgregarAlarma());
menuAlarmas.DropDownItems.Add("Ver Alarmas...", null, (s, e) => MostrarListaAlarmas());
menuContextualBandeja.Items.Add(menuAlarmas);
menuContextualBandeja.Items.Add(new ToolStripSeparator());
// Autoarranque
elementoMenuInicioAutomatico = new ToolStripMenuItem("Inicio Automático");
elementoMenuInicioAutomatico.Click += EnClickElementoInicioAutomatico;
menuContextualBandeja.Items.Add(elementoMenuInicioAutomatico);
menuContextualBandeja.Items.Add(new ToolStripSeparator());
menuContextualBandeja.Items.Add("Salir", null, (s, e) => SalirAplicacion());
iconoBandejaSistema.ContextMenuStrip = menuContextualBandeja;
}
private ToolStripMenuItem elementoMenuInicioAutomatico;
/// <summary>
/// Actualiza el texto del elemento de menú de inicio automático según su estado actual.
/// </summary>
private void ActualizarElementoMenuInicioAutomatico()
{
bool estaEnInicio = UtilidadesRegistro.EstaConfiguradoInicioAutomatico("RelojEscritorio");
elementoMenuInicioAutomatico.Checked = estaEnInicio;
elementoMenuInicioAutomatico.Text = estaEnInicio ? "Inicio Automático ✓" : "Inicio Automático";
}
/// <summary>
/// Inicia los temporizadores de actualización del reloj.
/// </summary>
private void IniciarRelojes()
{
temporizadorActualizacion.Start();
temporizadorSegundos.Start();
}
/// <summary>
/// Evento Tick del temporizador de actualización principal.
/// </summary>
private void EnActualizacionTemporizador(object sender, EventArgs e)
{
gestorAlarmas.VerificarAlarmas(); // Revisa si alguna alarma debe activarse
this.Invalidate(); // Fuerza un repintado para actualizar el reloj
}
/// <summary>
/// Evento Tick del temporizador de segundos (para animaciones).
/// </summary>
private void EnTemporizadorSegundos(object sender, EventArgs e)
{
// Solo se usa para la animación del segundero en el reloj analógico
if (estiloVisualActual == EstiloVisualReloj.Analogico)
{
this.Invalidate();
}
}
/// <summary>
/// Sobrescribe el método OnPaint para dibujar el reloj.
/// </summary>
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Graphics g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
// Dibuja el reloj según el estilo seleccionado
switch (estiloVisualActual)
{
case EstiloVisualReloj.Digital:
DibujarRelojDigital(g);
break;
case EstiloVisualReloj.Analogico:
DibujarRelojAnalogico(g);
break;
case EstiloVisualReloj.Minimalista:
DibujarRelojMinimalista(g);
break;
}
}
/// <summary>
/// Dibuja el reloj en estilo digital.
/// </summary>
private void DibujarRelojDigital(Graphics g)
{
DateTime ahora = DateTime.Now;
// Formatea las cadenas de texto
string textoHora = ahora.ToString(mostrarSegundos ? "HH:mm:ss" : "HH:mm");
string textoFecha = ahora.ToString("yyyy-MM-dd");
string textoDiaSemana = ObtenerNombreDiaSemanaEsp(ahora.DayOfWeek);
// Mide el tamaño del texto
SizeF tamanoHora = g.MeasureString(textoHora, fuenteReloj);
SizeF tamanoFecha = mostrarFecha ? g.MeasureString(textoFecha, fuenteReloj) : SizeF.Empty;
SizeF tamanoDiaSemana = mostrarDiaSemana ? g.MeasureString(textoDiaSemana, fuenteReloj) : SizeF.Empty;
// Calcula la altura total para centrar verticalmente
float alturaTotal = tamanoHora.Height;
if (mostrarFecha) alturaTotal += tamanoFecha.Height + 5;
if (mostrarDiaSemana) alturaTotal += tamanoDiaSemana.Height + 5;
float inicioY = (this.ClientSize.Height - alturaTotal) / 2;
float posicionYActual = inicioY;
// Dibuja la hora
DibujarTextoConSombra(g, textoHora, fuenteReloj,
new PointF((this.ClientSize.Width - tamanoHora.Width) / 2, posicionYActual),
colorReloj, colorSombra, mostrarSombra ? 2 : 0);
posicionYActual += tamanoHora.Height + 5;
// Dibuja la fecha
if (mostrarFecha)
{
DibujarTextoConSombra(g, textoFecha, fuenteReloj,
new PointF((this.ClientSize.Width - tamanoFecha.Width) / 2, posicionYActual),
colorReloj, colorSombra, mostrarSombra ? 2 : 0);
posicionYActual += tamanoFecha.Height + 5;
}
// Dibuja el día de la semana
if (mostrarDiaSemana)
{
DibujarTextoConSombra(g, textoDiaSemana, fuenteReloj,
new PointF((this.ClientSize.Width - tamanoDiaSemana.Width) / 2, posicionYActual),
colorReloj, colorSombra, mostrarSombra ? 2 : 0);
}
}
/// <summary>
/// Dibuja el reloj en estilo analógico.
/// </summary>
private void DibujarRelojAnalogico(Graphics g)
{
DateTime ahora = DateTime.Now;
int centroX = this.ClientSize.Width / 2;
int centroY = this.ClientSize.Height / 2;
int radio = Math.Min(centroX, centroY) - 20;
// Dibuja el círculo exterior del reloj
using (Pen pen = new Pen(colorReloj, 2))
{
g.DrawEllipse(pen, centroX - radio, centroY - radio, radio * 2, radio * 2);
}
// Dibuja las marcas de las horas y minutos
for (int i = 0; i < 60; i++)
{
double angulo = i * 6 * Math.PI / 180;
int longitudMarca = (i % 5 == 0) ? 10 : 5; // Marcas más largas para las horas
int anchoMarca = (i % 5 == 0) ? 2 : 1;
int x1 = (int)(centroX + (radio - 5) * Math.Sin(angulo));
int y1 = (int)(centroY - (radio - 5) * Math.Cos(angulo));
int x2 = (int)(centroX + (radio - 5 - longitudMarca) * Math.Sin(angulo));
int y2 = (int)(centroY - (radio - 5 - longitudMarca) * Math.Cos(angulo));
using (Pen pen = new Pen(colorReloj, anchoMarca))
{
g.DrawLine(pen, x1, y1, x2, y2);
}
}
// Dibuja los números de las horas
for (int i = 1; i <= 12; i++)
{
double angulo = (i * 30 - 90) * Math.PI / 180;
int radioNumeros = radio - 25;
int x = (int)(centroX + radioNumeros * Math.Cos(angulo));
int y = (int)(centroY + radioNumeros * Math.Sin(angulo));
string num = i.ToString();
SizeF tamanoNum = g.MeasureString(num, new Font(fuenteReloj.FontFamily, 12));
using (Brush brush = new SolidBrush(colorReloj))
{
g.DrawString(num, new Font(fuenteReloj.FontFamily, 12), brush,
x - tamanoNum.Width / 2, y - tamanoNum.Height / 2);
}
}
// Dibuja la manecilla de las horas
double anguloHoras = (ahora.Hour % 12 + ahora.Minute / 60.0) * 30 * Math.PI / 180;
int longitudHoras = (int)(radio * 0.5);
int horasX = (int)(centroX + longitudHoras * Math.Sin(anguloHoras));
int horasY = (int)(centroY - longitudHoras * Math.Cos(anguloHoras));
using (Pen pen = new Pen(colorReloj, 4)) { g.DrawLine(pen, centroX, centroY, horasX, horasY); }
// Dibuja la manecilla de los minutos
double anguloMinutos = (ahora.Minute + ahora.Second / 60.0) * 6 * Math.PI / 180;
int longitudMinutos = (int)(radio * 0.7);
int minutosX = (int)(centroX + longitudMinutos * Math.Sin(anguloMinutos));
int minutosY = (int)(centroY - longitudMinutos * Math.Cos(anguloMinutos));
using (Pen pen = new Pen(colorReloj, 3)) { g.DrawLine(pen, centroX, centroY, minutosX, minutosY); }
// Dibuja la manecilla de los segundos (si está activada)
if (mostrarSegundos)
{
double anguloSegundos = (ahora.Second + ahora.Millisecond / 1000.0) * 6 * Math.PI / 180;
int longitudSegundos = (int)(radio * 0.8);
int segundosX = (int)(centroX + longitudSegundos * Math.Sin(anguloSegundos));
int segundosY = (int)(centroY - longitudSegundos * Math.Cos(anguloSegundos));
using (Pen pen = new Pen(Color.Red, 1)) { g.DrawLine(pen, centroX, centroY, segundosX, segundosY); }
using (Brush brush = new SolidBrush(Color.Red)) { g.FillEllipse(brush, segundosX - 3, segundosY - 3, 6, 6); }
}
// Dibuja el punto central
using (Brush brush = new SolidBrush(colorReloj)) { g.FillEllipse(brush, centroX - 4, centroY - 4, 8, 8); }
// Dibuja la fecha (si está activada)
if (mostrarFecha)
{
string textoFechaAnalogico = ahora.ToString("MM/dd");
SizeF tamanoFechaAnalogico = g.MeasureString(textoFechaAnalogico, new Font(fuenteReloj.FontFamily, 10));
using (Brush brush = new SolidBrush(colorReloj))
{
g.DrawString(textoFechaAnalogico, new Font(fuenteReloj.FontFamily, 10), brush,
centroX - tamanoFechaAnalogico.Width / 2, centroY + radio / 2);
}
}
}
/// <summary>
/// Dibuja el reloj en estilo minimalista (solo horas y minutos).
/// </summary>
private void DibujarRelojMinimalista(Graphics g)
{
DateTime ahora = DateTime.Now;
string textoHoraMinimalista = ahora.ToString("HH:mm");
using (Font fuenteGrande = new Font(fuenteReloj.FontFamily, 36, FontStyle.Bold))
{
SizeF tamanoHora = g.MeasureString(textoHoraMinimalista, fuenteGrande);
DibujarTextoConSombra(g, textoHoraMinimalista, fuenteGrande,
new PointF((this.ClientSize.Width - tamanoHora.Width) / 2, 20),
colorReloj, colorSombra, mostrarSombra ? 3 : 0);
}
}
/// <summary>
/// Dibuja texto con una sombra para un efecto de profundidad.
/// </summary>
private void DibujarTextoConSombra(Graphics g, string texto, Font fuente,
PointF ubicacion, Color colorTexto, Color colorSombra, int desplazamientoSombra)
{
if (desplazamientoSombra > 0)
{
using (Brush pincelSombra = new SolidBrush(colorSombra))
{
g.DrawString(texto, fuente, pincelSombra,
ubicacion.X + desplazamientoSombra, ubicacion.Y + desplazamientoSombra);
}
}
using (Brush pincelTexto = new SolidBrush(colorTexto))
{
g.DrawString(texto, fuente, pincelTexto, ubicacion);
}
}
/// <summary>
/// Retorna el nombre del día de la semana en español.
/// </summary>
private string ObtenerNombreDiaSemanaEsp(DayOfWeek dia)
{
switch (dia)
{
case DayOfWeek.Sunday: return "Domingo";
case DayOfWeek.Monday: return "Lunes";
case DayOfWeek.Tuesday: return "Martes";
case DayOfWeek.Wednesday: return "Miércoles";
case DayOfWeek.Thursday: return "Jueves";
case DayOfWeek.Friday: return "Viernes";
case DayOfWeek.Saturday: return "Sábado";
default: return "";
}
}
// --- Manejadores de Eventos del Ratón ---
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
if (e.Button == MouseButtons.Left)
{
estaArrastrando = true;
puntoInicioArrastre = new Point(e.X, e.Y);
this.Cursor = Cursors.SizeAll;
}
else if (e.Button == MouseButtons.Right)
{
menuContextualBandeja.Show(this, e.Location);
}
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (estaArrastrando)
{
Point nuevaUbicacion = this.Location;
nuevaUbicacion.Offset(e.X - puntoInicioArrastre.X, e.Y - puntoInicioArrastre.Y);
this.Location = nuevaUbicacion;
}
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
if (e.Button == MouseButtons.Left)
{
estaArrastrando = false;
this.Cursor = Cursors.Default;
GuardarAjustes(); // Guarda la nueva posición
}
}
protected override void OnMouseDoubleClick(MouseEventArgs e)
{
base.OnMouseDoubleClick(e);
if (e.Button == MouseButtons.Left)
{
// Doble clic alterna el estilo del reloj
estiloVisualActual = (EstiloVisualReloj)(((int)estiloVisualActual + 1) % 3);
this.Invalidate();
GuardarAjustes();
}
}
// --- Manejadores de Eventos de Alarma ---
private void EnAlarmaActivada(object sender, ArgumentosEventoAlarma e)
{
// Muestra la notificación en el hilo de la UI
this.Invoke(new Action(() => { MostrarNotificacionAlarma(e.Alarma); }));
}
private void MostrarNotificacionAlarma(Alarma alarma)
{
SystemSounds.Exclamation.Play(); // Reproduce un sonido de alarma
if (iconoBandejaSistema.Visible)
{
iconoBandejaSistema.ShowBalloonTip(5000, "Alerta de Alarma",
$"{alarma.Nombre}\n{alarma.HoraActivacion:HH:mm}", ToolTipIcon.Info);
}
using (var ventanaAlarma = new FormularioAlarma(alarma))
{
ventanaAlarma.ShowDialog();
}
}
// --- Funciones del Menú ---
private void CambiarEstiloReloj(EstiloVisualReloj estilo)
{
estiloVisualActual = estilo;
this.Invalidate();
GuardarAjustes();
}
private void CambiarOpacidad()
{
indiceOpacidadActual = (indiceOpacidadActual + 1) % nivelesOpacidad.Length;
this.Opacity = nivelesOpacidad[indiceOpacidadActual];
GuardarAjustes();
}
private void CambiarColor()
{
using (ColorDialog dialogoColor = new ColorDialog { Color = colorReloj, FullOpen = true })
{
if (dialogoColor.ShowDialog() == DialogResult.OK)
{
colorReloj = dialogoColor.Color;
this.Invalidate();
GuardarAjustes();
}
}
}
private void AlternarMostrarSegundos() { mostrarSegundos = !mostrarSegundos; GuardarAjustes(); this.Invalidate(); }
private void AlternarMostrarFecha() { mostrarFecha = !mostrarFecha; GuardarAjustes(); this.Invalidate(); }
private void AlternarMostrarDiaSemana() { mostrarDiaSemana = !mostrarDiaSemana; GuardarAjustes(); this.Invalidate(); }
private void AgregarAlarma()
{
using (var formularioAlarma = new FormularioAlarma())
{
if (formularioAlarma.ShowDialog() == DialogResult.OK)
{
gestorAlarmas.AñadirAlarma(formularioAlarma.AlarmaConfigurada);
GuardarAlarmas();
}
}
}
private void MostrarListaAlarmas()
{
using (var formularioListaAlarmas = new FormularioListaAlarmas(gestorAlarmas))
{
formularioListaAlarmas.ShowDialog();
GuardarAlarmas();
}
}
private void EnClickElementoInicioAutomatico(object sender, EventArgs e)
{
bool estaEnInicio = UtilidadesRegistro.EstaConfiguradoInicioAutomatico("RelojEscritorio");
if (estaEnInicio)
{
UtilidadesRegistro.EliminarDeInicioAutomatico("RelojEscritorio");
}
else
{
UtilidadesRegistro.ConfigurarInicioAutomatico("RelojEscritorio", Application.ExecutablePath);
}
ActualizarElementoMenuInicioAutomatico();
}
private void GuardarAlarmas()
{
AjustesAplicacion.Default.AlarmasSerializadas = gestorAlarmas.SerializarAlarmasAXml();
AjustesAplicacion.Default.Save();
}
private void EnClickIconoBandeja(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
this.Visible = !this.Visible; // Alterna la visibilidad de la ventana
}
}
private void SalirAplicacion()
{
GuardarAjustes();
GuardarAlarmas();
temporizadorActualizacion.Stop();
temporizadorSegundos.Stop();
iconoBandejaSistema.Visible = false; // Oculta el icono antes de salir
Application.Exit();
}
/// <summary>
/// Maneja el evento de cierre del formulario para minimizarlo a la bandeja.
/// </summary>
protected override void OnFormClosing(FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.UserClosing)
{
e.Cancel = true; // Cancela el cierre real
this.Hide(); // Oculta la ventana
iconoBandejaSistema.ShowBalloonTip(1000, "Reloj de Escritorio", "El reloj se ha minimizado a la bandeja del sistema.", ToolTipIcon.Info);
}
base.OnFormClosing(e);
}
/// <summary>
/// Enumeración para los diferentes estilos visuales del reloj.
/// </summary>
private enum EstiloVisualReloj
{
Digital = 0,
Analogico = 1,
Minimalista = 2
}
}
/// <summary>
/// Clase serializable para guardar la posición y tamaño de la ventana.
/// </summary>
[Serializable]
public class PosicionVentana
{
public Point Ubicacion { get; set; }
public Size Dimensiones { get; set; }
public PosicionVentana() { }
public PosicionVentana(Point ubicacion, Size dimensiones)
{
Ubicacion = ubicacion;
Dimensiones = dimensiones;
}
public bool EsValida()
{
return Ubicacion.X >= 0 && Ubicacion.Y >= 0 &&
Dimensiones.Width > 0 && Dimensiones.Height > 0;
}
}
}
3. Diseño de la Ventana Principal (VentanaPrincipal.Designer.cs)
Este archivo es generado por el diseñador de Windows Forms y contiene la inicialización de los componentes visuales básicos.
namespace RelojEscritorio.Vistas
{
partial class VentanaPrincipal
{
/// <summary>
/// Variable de diseñador requerida.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Limpiar los recursos que se estén utilizando.
/// </summary>
/// <param name="disposing">true si los recursos administrados deben desecharse; false en caso contrario.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Código generado por el Diseñador de Windows Forms
/// <summary>
/// Método necesario para admitir el Diseñador. No se puede modificar
/// el contenido de este método con el editor de código.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(300, 150);
this.Name = "VentanaPrincipal";
this.Text = "Reloj de Escritorio";
}
#endregion
}
}
4. Gestor de Alarmas (GestorAlarmas.cs)
Esta clase se encarga de crear, almacenar, cargar y verificar las alarmas para su activación.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Serialization;
using System.IO;
namespace RelojEscritorio.Modelos
{
public class GestorAlarmas
{
private List<Alarma> listaAlarmas = new List<Alarma>();
private System.Windows.Forms.Timer temporizadorVerificacion;
public event EventHandler<ArgumentosEventoAlarma> AlarmaActivada;
public GestorAlarmas()
{
temporizadorVerificacion = new System.Windows.Forms.Timer();
temporizadorVerificacion.Interval = 1000; // Verifica cada segundo
temporizadorVerificacion.Tick += EnTemporizadorVerificacion;
temporizadorVerificacion.Start();
}
public void AñadirAlarma(Alarma alarmaNueva)
{
listaAlarmas.Add(alarmaNueva);
}
public void EliminarAlarma(Alarma alarmaAEliminar)
{
listaAlarmas.Remove(alarmaAEliminar);
}
public List<Alarma> ObtenerAlarmas()
{
return new List<Alarma>(listaAlarmas); // Retorna una copia para evitar modificaciones externas directas
}
/// <summary>
/// Itera sobre las alarmas para verificar si alguna debe activarse.
/// </summary>
public void VerificarAlarmas()
{
DateTime momentoActual = DateTime.Now;
foreach (var alarma in listaAlarmas.Where(a => a.EstaHabilitada).ToList()) // ToList para evitar modificación durante iteración
{
if (alarma.DebeActivarse(momentoActual))
{
AlAlarmaActivada(alarma);
// Si la alarma no se repite, la deshabilita después de activarse
if (!alarma.SeRepite)
{
alarma.EstaHabilitada = false;
}
}
}
}
private void EnTemporizadorVerificacion(object sender, EventArgs e)
{
VerificarAlarmas();
}
protected virtual void AlAlarmaActivada(Alarma alarma)
{
AlarmaActivada?.Invoke(this, new ArgumentosEventoAlarma(alarma));
}
/// <summary>
/// Serializa la lista de alarmas a una cadena XML.
/// </summary>
public string SerializarAlarmasAXml()
{
try
{
var serializador = new XmlSerializer(typeof(List<Alarma>));
using (var escritor = new StringWriter())
{
serializador.Serialize(escritor, listaAlarmas);
return escritor.ToString();
}
}
catch
{
return string.Empty; // Retorna vacío en caso de error
}
}
/// <summary>
/// Deserializa una cadena XML a una lista de alarmas.
/// </summary&
public void DeserializarAlarmasDesdeXml(string xml)
{
try
{
if (!string.IsNullOrEmpty(xml))
{
var serializador = new XmlSerializer(typeof(List<Alarma>));
using (var lector = new StringReader(xml))
{
var alarmasDeserializadas = (List<Alarma>)serializador.Deserialize(lector);
listaAlarmas = alarmasDeserializadas ?? new List<Alarma>();
}
}
}
catch
{
listaAlarmas = new List<Alarma>(); // Inicializa vacío si hay un error de deserialización
}
}
}
/// <summary>
/// Representa una alarma individual con sus propiedades.
/// </summary>
[Serializable]
public class Alarma
{
public string Nombre { get; set; }
public DateTime HoraActivacion { get; set; }
public bool EstaHabilitada { get; set; }
public bool SeRepite { get; set; }
public DayOfWeek[] DiasRepeticion { get; set; }
public DateTime UltimaActivacion { get; set; }
public Alarma()
{
Nombre = "Nueva Alarma";
HoraActivacion = DateTime.Now.AddMinutes(1); // Por defecto, un minuto en el futuro
EstaHabilitada = true;
SeRepite = false;
DiasRepeticion = new DayOfWeek[0];
UltimaActivacion = DateTime.MinValue; // Asegura que se active la primera vez
}
/// <summary>
/// Determina si la alarma debe activarse en el momento actual.
/// </summary>
public bool DebeActivarse(DateTime momentoActual)
{
if (!EstaHabilitada) return false;
// Compara solo la hora y minuto para alarmas diarias/repetitivas
bool esLaHora = momentoActual.Hour == HoraActivacion.Hour &&
momentoActual.Minute == HoraActivacion.Minute;
if (esLaHora)
{
// Evita múltiples activaciones en el mismo minuto
if ((momentoActual - UltimaActivacion).TotalMinutes < 1)
return false;
if (SeRepite)
{
// Si se repite y no hay días específicos o el día actual está en la lista
if (DiasRepeticion.Length == 0 || DiasRepeticion.Contains(momentoActual.DayOfWeek))
{
UltimaActivacion = momentoActual;
return true;
}
}
else
{
// Alarma única, se activa y se registra la última activación
UltimaActivacion = momentoActual;
return true;
}
}
return false;
}
}
/// <summary>
/// Argumentos para el evento de activación de alarma.
/// </summary>
public class ArgumentosEventoAlarma : EventArgs
{
public Alarma Alarma { get; }
public ArgumentosEventoAlarma(Alarma alarma)
{
Alarma = alarma;
}
}
}
5. Formulario de Configuración de Alarma (FormularioAlarma.cs)
Este formulario permite al usuario configurar los detalles de una nueva alarma o modificar una existente.
using System;
using System.Linq;
using System.Windows.Forms;
using System.Drawing;
using RelojEscritorio.Modelos;
namespace RelojEscritorio.Vistas
{
public partial class FormularioAlarma : Form
{
public Alarma AlarmaConfigurada { get; private set; }
private CheckBox[] casillasDiasSemana = new CheckBox[7];
private string[] nombresDias = { "Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado", "Domingo" };
private DateTimePicker selectorHora;
private Panel panelDiasRepeticion;
private TextBox campoNombreAlarma;
private CheckBox casillaRepetir;
private CheckBox casillaHabilitada;
public FormularioAlarma() : this(null) { }
public FormularioAlarma(Alarma alarmaExistente)
{
InitializeComponentManual(); // Usar un método de inicialización manual
InicializarCasillasDias();
if (alarmaExistente != null)
{
AlarmaConfigurada = alarmaExistente;
CargarDatosAlarma();
}
else
{
AlarmaConfigurada = new Alarma();
selectorHora.Value = DateTime.Now.AddMinutes(1); // Hora por defecto
}
}
/// <summary>
/// Inicializa los componentes del formulario manualmente.
/// </summary>
private void InitializeComponentManual()
{
this.Text = "Configurar Alarma";
this.Size = new Size(350, 320);
this.FormBorderStyle = FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.StartPosition = FormStartPosition.CenterScreen;
// Etiqueta y campo para el nombre
var etiquetaNombre = new Label { Text = "Nombre:", Location = new Point(20, 20), Size = new Size(80, 20) };
campoNombreAlarma = new TextBox { Location = new Point(100, 20), Size = new Size(200, 20) };
campoNombreAlarma.TextChanged += (s, e) => AlarmaConfigurada.Nombre = campoNombreAlarma.Text;
// Etiqueta y selector de hora
var etiquetaHora = new Label { Text = "Hora:", Location = new Point(20, 50), Size = new Size(80, 20) };
selectorHora = new DateTimePicker
{
Location = new Point(100, 50),
Size = new Size(100, 20),
Format = DateTimePickerFormat.Time,
ShowUpDown = true
};
selectorHora.ValueChanged += (s, e) => AlarmaConfigurada.HoraActivacion = selectorHora.Value;
// Casilla de verificación para repetir
casillaRepetir = new CheckBox
{
Text = "Repetir",
Location = new Point(20, 80),
Size = new Size(100, 20)
};
casillaRepetir.CheckedChanged += (s, e) =>
{
AlarmaConfigurada.SeRepite = casillaRepetir.Checked;
panelDiasRepeticion.Enabled = casillaRepetir.Checked;
};
// Panel para las casillas de los días de la semana
panelDiasRepeticion = new Panel
{
Location = new Point(20, 110),
Size = new Size(300, 70),
Enabled = false,
BorderStyle = BorderStyle.FixedSingle
};
// Casilla de verificación para habilitar/deshabilitar alarma
casillaHabilitada = new CheckBox
{
Text = "Alarma Habilitada",
Location = new Point(20, 190),
Size = new Size(150, 20),
Checked = true
};
casillaHabilitada.CheckedChanged += (s, e) => AlarmaConfigurada.EstaHabilitada = casillaHabilitada.Checked;
// Botones de acción
var botonOk = new Button
{
Text = "Aceptar",
Location = new Point(150, 240),
Size = new Size(80, 30),
DialogResult = DialogResult.OK
};
var botonCancelar = new Button
{
Text = "Cancelar",
Location = new Point(240, 240),
Size = new Size(80, 30),
DialogResult = DialogResult.Cancel
};
this.Controls.AddRange(new Control[]
{
etiquetaNombre, campoNombreAlarma, etiquetaHora, selectorHora,
casillaRepetir, panelDiasRepeticion, casillaHabilitada,
botonOk, botonCancelar
});
this.AcceptButton = botonOk;
this.CancelButton = botonCancelar;
}
/// <summary>
/// Inicializa dinámicamente las casillas de verificación para los días de la semana.
/// </summary>
private void InicializarCasillasDias()
{
int xOffset = 5;
int yOffset = 5;
for (int i = 0; i < 7; i++)
{
casillasDiasSemana[i] = new CheckBox
{
Text = nombresDias[i],
Location = new Point(xOffset + (i % 4) * 70, yOffset + (i / 4) * 25), // Organiza en dos filas
Size = new Size(70, 20),
Tag = (DayOfWeek)(((i + 1) % 7)) // DayOfWeek.Sunday es 0, DayOfWeek.Monday es 1... ajustar para que Lunes sea el primer día visualmente
};
// Ajuste para que el tag mapee correctamente de Lunes a Domingo
// Si i=0 (Lunes) Tag=1, si i=6 (Domingo) Tag=0
if (i == 6) { // Domingo es el último en la lista nombresDias
casillasDiasSemana[i].Tag = DayOfWeek.Sunday;
} else {
casillasDiasSemana[i].Tag = (DayOfWeek)(i + 1);
}
casillasDiasSemana[i].CheckedChanged += (s, e) => { ActualizarDiasRepeticion(); };
panelDiasRepeticion.Controls.Add(casillasDiasSemana[i]);
}
}
/// <summary>
/// Carga los datos de una alarma existente en el formulario.
/// </summary>
private void CargarDatosAlarma()
{
if (AlarmaConfigurada == null) return;
campoNombreAlarma.Text = AlarmaConfigurada.Nombre;
selectorHora.Value = AlarmaConfigurada.HoraActivacion;
casillaRepetir.Checked = AlarmaConfigurada.SeRepite;
casillaHabilitada.Checked = AlarmaConfigurada.EstaHabilitada;
// Establece los días de repetición
if (AlarmaConfigurada.DiasRepeticion != null)
{
foreach (var checkbox in casillasDiasSemana)
{
checkbox.Checked = AlarmaConfigurada.DiasRepeticion.Contains((DayOfWeek)checkbox.Tag);
}
}
panelDiasRepeticion.Enabled = casillaRepetir.Checked; // Asegura que el panel esté habilitado/deshabilitado correctamente
}
/// <summary>
/// Actualiza la lista de días de repetición de la alarma según las casillas marcadas.
/// </summary>
private void ActualizarDiasRepeticion()
{
var diasSeleccionados = new List<DayOfWeek>();
foreach (var checkbox in casillasDiasSemana)
{
if (checkbox.Checked)
{
diasSeleccionados.Add((DayOfWeek)checkbox.Tag);
}
}
AlarmaConfigurada.DiasRepeticion = diasSeleccionados.ToArray();
}
}
}
6. Formulario de Lista de Alarmas (FormularioListaAlarmas.cs)
Este formulario muestra una lista de todas las alarmas configuradas y permite gestionarlas (añadir, editar, eliminar, habilitar/deshabilitar).
using System;
using System.Windows.Forms;
using System.Drawing;
using System.Linq; // Necesario para .ToList()
using RelojEscritorio.Modelos;
namespace RelojEscritorio.Vistas
{
public partial class FormularioListaAlarmas : Form
{
private GestorAlarmas gestorAlarmas;
private ListView vistaListaAlarmas;
public FormularioListaAlarmas(GestorAlarmas manager)
{
gestorAlarmas = manager;
InitializeComponentManual();
CargarAlarmasEnVista();
}
/// <summary>
/// Inicializa los componentes del formulario manualmente.
/// </summary>
private void InitializeComponentManual()
{
this.Text = "Lista de Alarmas";
this.Size = new Size(600, 350);
this.FormBorderStyle = FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.StartPosition = FormStartPosition.CenterScreen;
// Configuración del ListView
vistaListaAlarmas = new ListView
{
View = View.Details,
FullRowSelect = true,
GridLines = true,
Location = new Point(10, 10),
Size = new Size(560, 220)
};
// Añadir columnas
vistaListaAlarmas.Columns.Add("Nombre", 120);
vistaListaAlarmas.Columns.Add("Hora", 80);
vistaListaAlarmas.Columns.Add("Repetir", 60);
vistaListaAlarmas.Columns.Add("Estado", 80);
vistaListaAlarmas.Columns.Add("Días de Repetición", 200);
// Botones de acción
var botonAñadir = new Button
{
Text = "Añadir",
Location = new Point(10, 250),
Size = new Size(80, 30)
};
botonAñadir.Click += AlClickBotonAñadir;
var botonEditar = new Button
{
Text = "Editar",
Location = new Point(100, 250),
Size = new Size(80, 30)
};
botonEditar.Click += AlClickBotonEditar;
var botonEliminar = new Button
{
Text = "Eliminar",
Location = new Point(190, 250),
Size = new Size(80, 30)
};
botonEliminar.Click += AlClickBotonEliminar;
var botonAlternarEstado = new Button
{
Text = "Activar/Desactivar",
Location = new Point(280, 250),
Size = new Size(120, 30)
};
botonAlternarEstado.Click += AlClickBotonAlternarEstado;
var botonCerrar = new Button
{
Text = "Cerrar",
Location = new Point(480, 250),
Size = new Size(80, 30),
DialogResult = DialogResult.Cancel
};
this.Controls.AddRange(new Control[]
{
vistaListaAlarmas, botonAñadir, botonEditar,
botonEliminar, botonAlternarEstado, botonCerrar
});
this.CancelButton = botonCerrar;
}
/// <summary>
/// Carga todas las alarmas en el ListView.
/// </summary>
private void CargarAlarmasEnVista()
{
vistaListaAlarmas.Items.Clear();
foreach (var alarma in gestorAlarmas.ObtenerAlarmas())
{
var item = new ListViewItem(alarma.Nombre);
item.SubItems.Add(alarma.HoraActivacion.ToString("HH:mm"));
item.SubItems.Add(alarma.SeRepite ? "Sí" : "No");
item.SubItems.Add(alarma.EstaHabilitada ? "Activada" : "Desactivada");
string textoDias = "";
if (alarma.DiasRepeticion != null && alarma.DiasRepeticion.Length > 0)
{
// Ordenar días para una presentación consistente (Lunes a Domingo)
var diasOrdenados = alarma.DiasRepeticion
.OrderBy(d => d == DayOfWeek.Sunday ? 7 : (int)d)
.ToList();
foreach (var dia in diasOrdenados)
{
textoDias += ObtenerAbreviaturaDia(dia) + " ";
}
}
item.SubItems.Add(textoDias.Trim());
item.Tag = alarma; // Almacena el objeto Alarma en el Tag del ListViewItem
vistaListaAlarmas.Items.Add(item);
}
}
/// <summary>
/// Retorna la abreviatura en español para un día de la semana.
/// </summary>
private string ObtenerAbreviaturaDia(DayOfWeek dia)
{
switch (dia)
{
case DayOfWeek.Monday: return "Lun";
case DayOfWeek.Tuesday: return "Mar";
case DayOfWeek.Wednesday: return "Mié";
case DayOfWeek.Thursday: return "Jue";
case DayOfWeek.Friday: return "Vie";
case DayOfWeek.Saturday: return "Sáb";
case DayOfWeek.Sunday: return "Dom";
default: return "";
}
}
private void AlClickBotonAñadir(object sender, EventArgs e)
{
using (var formularioAlarma = new FormularioAlarma())
{
if (formularioAlarma.ShowDialog() == DialogResult.OK)
{
gestorAlarmas.AñadirAlarma(formularioAlarma.AlarmaConfigurada);
CargarAlarmasEnVista(); // Actualiza la lista
}
}
}
private void AlClickBotonEditar(object sender, EventArgs e)
{
if (vistaListaAlarmas.SelectedItems.Count > 0)
{
var alarmaAEditar = (Alarma)vistaListaAlarmas.SelectedItems[0].Tag;
using (var formularioAlarma = new FormularioAlarma(alarmaAEditar))
{
if (formularioAlarma.ShowDialog() == DialogResult.OK)
{
CargarAlarmasEnVista(); // Actualiza la lista
}
}
}
else
{
MessageBox.Show("Selecciona una alarma para editar.", "Advertencia", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
private void AlClickBotonEliminar(object sender, EventArgs e)
{
if (vistaListaAlarmas.SelectedItems.Count > 0)
{
if (MessageBox.Show("¿Estás seguro de que quieres eliminar esta alarma?", "Confirmar Eliminación",
MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
{
var alarmaAEliminar = (Alarma)vistaListaAlarmas.SelectedItems[0].Tag;
gestorAlarmas.EliminarAlarma(alarmaAEliminar);
CargarAlarmasEnVista(); // Actualiza la lista
}
}
else
{
MessageBox.Show("Selecciona una alarma para eliminar.", "Advertencia", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
private void AlClickBotonAlternarEstado(object sender, EventArgs e)
{
if (vistaListaAlarmas.SelectedItems.Count > 0)
{
var alarmaAAlternar = (Alarma)vistaListaAlarmas.SelectedItems[0].Tag;
alarmaAAlternar.EstaHabilitada = !alarmaAAlternar.EstaHabilitada;
CargarAlarmasEnVista(); // Actualiza la lista
}
else
{
MessageBox.Show("Selecciona una alarma para cambiar su estado.", "Advertencia", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
}
}
7. Utilidades del Registro (UtilidadesRegistro.cs)
Esta clase estática proporciona métodos para interactuar con el registro de Windows, específicamente para configurar y desconfigurar el inicio automático de la aplicación.
using System;
using Microsoft.Win32;
using System.Security;
using System.Windows.Forms;
namespace RelojEscritorio.Utilidades
{
public static class UtilidadesRegistro
{
private const string CLAVE_REGISTRO_RUN = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Run";
/// <summary>
/// Configura la aplicación para que se inicie automáticamente con Windows.
/// </summary>
/// <param name="nombreApp">Nombre de la aplicación.</param>
/// <param name="rutaApp">Ruta completa del ejecutable de la aplicación.</param>
/// <returns>True si se configuró correctamente, false en caso contrario.</returns>
public static bool ConfigurarInicioAutomatico(string nombreApp, string rutaApp)
{
try
{
using (RegistryKey clave = Registry.CurrentUser.OpenSubKey(CLAVE_REGISTRO_RUN, true))
{
if (clave != null)
{
clave.SetValue(nombreApp, $""{rutaApp}"");
return true;
}
}
}
catch (SecurityException ex)
{
MessageBox.Show($"Permiso denegado: {ex.Message}", "Error de Seguridad",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
catch (UnauthorizedAccessException ex)
{
MessageBox.Show($"Acceso no autorizado: {ex.Message}", "Error de Acceso",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
catch (Exception ex)
{
MessageBox.Show($"Fallo al configurar inicio: {ex.Message}", "Error",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
return false;
}
/// <summary>
/// Elimina la aplicación de la lista de inicio automático de Windows.
/// </summary>
/// <param name="nombreApp">Nombre de la aplicación.</param>
/// <returns>True si se eliminó correctamente, false en caso contrario.</returns>
public static bool EliminarDeInicioAutomatico(string nombreApp)
{
try
{
using (RegistryKey clave = Registry.CurrentUser.OpenSubKey(CLAVE_REGISTRO_RUN, true))
{
if (clave != null && clave.GetValue(nombreApp) != null)
{
clave.DeleteValue(nombreApp, false);
return true;
}
}
}
catch (Exception ex)
{
MessageBox.Show($"Fallo al eliminar de inicio: {ex.Message}", "Error",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
return false;
}
/// <summary>
/// Verifica si la aplicación está configurada para iniciarse automáticamente.
/// </summary>
/// <param name="nombreApp">Nombre de la aplicación.</param>
/// <returns>True si está configurada, false en caso contrario.</returns>
public static bool EstaConfiguradoInicioAutomatico(string nombreApp)
{
try
{
using (RegistryKey clave = Registry.CurrentUser.OpenSubKey(CLAVE_REGISTRO_RUN, false))
{
if (clave != null)
{
return clave.GetValue(nombreApp) != null;
}
}
}
catch
{
// Ignorar errores de acceso para esta verificación, asumir que no está configurado.
}
return false;
}
/// <summary>
/// Obtiene la ruta del ejecutable de una aplicación configurada para inicio automático.
/// </summary>
/// <param name="nombreApp">Nombre de la aplicación.</param>
/// <returns>La ruta del ejecutable o null si no se encuentra.</returns>
public static string ObtenerRutaInicioAutomatico(string nombreApp)
{
try
{
using (RegistryKey clave = Registry.CurrentUser.OpenSubKey(CLAVE_REGISTRO_RUN, false))
{
if (clave != null)
{
return clave.GetValue(nombreApp)?.ToString();
}
}
}
catch
{
// Ignorar errores.
}
return null;
}
}
}
8. Clase de Ajustes (AjustesAplicacion.cs)
Esta clase gestiona la persistencia de los ajustes del usuario, como la posición de la ventana, la opacidad y las preferencias de visualización del reloj.
using System;
using System.Drawing;
using System.Configuration;
using RelojEscritorio.Modelos; // Necesario para PosicionVentana
namespace RelojEscritorio.Configuracion
{
internal sealed partial class AjustesAplicacion : ApplicationSettingsBase
{
private static AjustesAplicacion instanciaPorDefecto = (AjustesAplicacion)Synchronized(new AjustesAplicacion());
public static AjustesAplicacion Default
{
get { return instanciaPorDefecto; }
}
[UserScopedSetting]
[DefaultSettingValue("true")]
public bool ActualizacionRequerida
{
get { return (bool)this["ActualizacionRequerida"]; }
set { this["ActualizacionRequerida"] = value; }
}
[UserScopedSetting]
public PosicionVentana PosicionVentanaPrincipal
{
get
{
var valor = this["PosicionVentanaPrincipal"];
return valor as PosicionVentana ?? new PosicionVentana();
}
set { this["PosicionVentanaPrincipal"] = value; }
}
[UserScopedSetting]
[DefaultSettingValue("3")] // Índice por defecto para 0.9f
public int NivelOpacidad
{
get { return (int)this["NivelOpacidad"]; }
set { this["NivelOpacidad"] = value; }
}
[UserScopedSetting]
[DefaultSettingValue("0")] // Índice por defecto para EstiloVisualReloj.Digital
public int EstiloRelojSeleccionado
{
get { return (int)this["EstiloRelojSeleccionado"]; }
set { this["EstiloRelojSeleccionado"] = value; }
}
[UserScopedSetting]
[DefaultSettingValue("true")]
public bool MostrarSegundos
{
get { return (bool)this["MostrarSegundos"]; }
set { this["MostrarSegundos"] = value; }
}
[UserScopedSetting]
[DefaultSettingValue("true")]
public bool MostrarFecha
{
get { return (bool)this["MostrarFecha"]; }
set { this["MostrarFecha"] = value; }
}
[UserScopedSetting]
[DefaultSettingValue("true")]
public bool MostrarDiaSemana
{
get { return (bool)this["MostrarDiaSemana"]; }
set { this["MostrarDiaSemana"] = value; }
}
[UserScopedSetting]
[DefaultSettingValue("Lime")]
public Color ColorPrincipalReloj
{
get { return (Color)this["ColorPrincipalReloj"]; }
set { this["ColorPrincipalReloj"] = value; }
}
[UserScopedSetting]
[DefaultSettingValue("Arial")]
public string NombreFuente
{
get { return (string)this["NombreFuente"]; }
set { this["NombreFuente"] = value; }
}
[UserScopedSetting]
[DefaultSettingValue("24")]
public float TamañoFuente
{
get { return (float)this["TamañoFuente"]; }
set { this["TamañoFuente"] = value; }
}
[UserScopedSetting]
[DefaultSettingValue("")]
public string AlarmasSerializadas
{
get { return (string)this["AlarmasSerializadas"]; }
set { this["AlarmasSerializadas"] = value; }
}
}
}
9. Archivo de Configuración del Proyecto (App.config)
Este archivo XML define los ajustes por defecto de la aplicación y la versión del framework .NET utilizada.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="userSettings"
type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<section name="RelojEscritorio.Configuracion.AjustesAplicacion"
type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
allowExeDefinition="MachineToLocalUser" requirePermission="false" />
</sectionGroup>
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
</startup>
<userSettings>
<RelojEscritorio.Configuracion.AjustesAplicacion>
<setting name="ActualizacionRequerida" serializeAs="String">
<value>True</value>
</setting>
<setting name="PosicionVentanaPrincipal" serializeAs="String">
<value />
</setting>
<setting name="NivelOpacidad" serializeAs="String">
<value>3</value>
</setting>
<setting name="EstiloRelojSeleccionado" serializeAs="String">
<value>0</value>
</setting>
<setting name="MostrarSegundos" serializeAs="String">
<value>True</value>
</setting>
<setting name="MostrarFecha" serializeAs="String">
<value>True</value>
</setting>
<setting name="MostrarDiaSemana" serializeAs="String">
<value>True</value>
</setting>
<setting name="ColorPrincipalReloj" serializeAs="String">
<value>Lime</value>
</setting>
<setting name="NombreFuente" serializeAs="String">
<value>Arial</value>
</setting>
<setting name="TamañoFuente" serializeAs="String">
<value>24</value>
</setting>
<setting name="AlarmasSerializadas" serializeAs="String">
<value />
</setting>
</RelojEscritorio.Configuracion.AjustesAplicacion>
</userSettings>
</configuration>
Guía de Uso Rápido
Para interactuar con el reloj de escritorio, puedes seguir estas instrucciones:
- Arrastrar con Clic Izquierdo: Mueve el reloj a la posición deseada en el escritorio.
- Clic Derecho sobre el Reloj: Abre un menú contextual con opciones de configuración.
- Doble Clic sobre el Reloj: Cambia rápidamente entre los diferentes estilos de visualización del reloj (Digital, Analógico, Minimalista).
- Clic Izquierdo en el Ícono de la Bandeja: Alterna la visibilidad de la ventana del reloj.
- Clic Derecho en el Ícono de la Bandeja: Despliega el menú completo con todas las opciones de configuración y gestión de alarmas.
Estilos de Reloj Disponibles
- Reloj Digital: Muestra la hora, fecha y día de la semana en formato numérico.
- Reloj Analógico: Un diseño de esfera de reloj tradicional con manecillas.
- Reloj Minimalista: Una vista simple que muestra solo la hora (horas y minutos).
Funcionalidad de Alarmas
- Configuración de alarmas únicas o repetitivas.
- Selección de días específicos para las alarmas repetitivas.
- Notificaciones visuales y sonoras cuando se activa una alarma.