Skip to content

Commit a08a628

Browse files
authored
Merge pull request #2 from Rckov/develop
2 parents f44da0e + 32b5039 commit a08a628

37 files changed

+1210
-869
lines changed

src/Remote Desktop.sln

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio Version 17
4-
VisualStudioVersion = 17.13.35919.96 d17.13
4+
VisualStudioVersion = 17.13.35919.96
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RemoteDesktop", "RemoteDesktop\RemoteDesktop.csproj", "{D70472F4-C5FB-4FA5-8E14-738DDD4F9695}"
77
EndProject

src/RemoteDesktop/App.xaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
33
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
44
xmlns:hc="https://handyorg.github.io/handycontrol">
5+
56
<Application.Resources>
67
<ResourceDictionary>
78
<ResourceDictionary.MergedDictionaries>

src/RemoteDesktop/App.xaml.cs

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Microsoft.Extensions.DependencyInjection;
1+
using DryIoc;
22

33
using RemoteDesktop.Services.Interfaces;
44
using RemoteDesktop.ViewModels;
@@ -8,28 +8,39 @@
88

99
namespace RemoteDesktop;
1010

11-
public partial class App : Application
11+
public partial class App
1212
{
13-
private readonly IServiceProvider _provider;
13+
private readonly IContainer _container;
1414

1515
public App()
1616
{
17-
_provider = ConfigureServices(new ServiceCollection());
17+
_container = ConfigureContainer();
1818
}
1919

20+
/// <summary>
21+
/// Base directory of the application.
22+
/// </summary>
2023
public static string BaseDirectory => AppContext.BaseDirectory;
2124

22-
private ServiceProvider ConfigureServices(IServiceCollection services)
25+
/// <summary>
26+
/// Configures the DryIoc container by registering views and services.
27+
/// </summary>
28+
private IContainer ConfigureContainer()
2329
{
24-
services.AddViews();
25-
services.AddServices();
30+
var container = new Container();
2631

27-
return services.BuildServiceProvider();
32+
container.RegisterViews();
33+
container.RegisterServices();
34+
35+
return container;
2836
}
2937

38+
/// <summary>
39+
/// Handles application startup event.
40+
/// </summary>
3041
protected override void OnStartup(StartupEventArgs e)
3142
{
32-
var service = _provider.GetRequiredService<IWindowService>();
43+
var service = _container.Resolve<IWindowService>();
3344
service.Show<MainViewModel>();
3445
}
3546
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.ComponentModel;
4+
using System.Diagnostics;
5+
using System.Runtime.CompilerServices;
6+
7+
namespace RemoteDesktop.Common.Base;
8+
9+
/// <summary>
10+
/// Base class for all ViewModels providing property change notification.
11+
/// </summary>
12+
internal abstract class BaseViewModel : INotifyPropertyChanged
13+
{
14+
protected BaseViewModel()
15+
{
16+
InitializeCommands();
17+
}
18+
19+
/// <summary>
20+
/// Event raised when a property value changes.
21+
/// </summary>
22+
public event PropertyChangedEventHandler PropertyChanged;
23+
24+
/// <summary>
25+
/// Override this method to initialize commands in the derived ViewModel.
26+
/// </summary>
27+
public virtual void InitializeCommands()
28+
{ }
29+
30+
/// <summary>
31+
/// Logs an informational message along with the calling method name.
32+
/// </summary>
33+
public void LogInfo(string message, [CallerMemberName] string method = null)
34+
{
35+
Debug.WriteLine(
36+
$"Method: {method}\r\n" +
37+
$"Message: {message}"
38+
);
39+
}
40+
41+
/// <summary>
42+
/// Logs an error message with optional exception details and calling method name.
43+
/// </summary>
44+
public void LogError(string message, Exception exception = null, [CallerMemberName] string method = null)
45+
{
46+
Debug.WriteLine(
47+
$"Method: {method}\r\n" +
48+
$"Message: {message}\r\n" +
49+
$"Exception: {exception?.Message ?? "No exception"}"
50+
);
51+
}
52+
53+
/// <summary>
54+
/// Raises the PropertyChanged event for UI updates.
55+
/// </summary>
56+
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
57+
{
58+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
59+
}
60+
61+
/// <summary>
62+
/// Sets the backing field value and raises PropertyChanged if the value has changed.
63+
/// </summary>
64+
protected bool Set<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
65+
{
66+
if (EqualityComparer<T>.Default.Equals(field, value))
67+
{
68+
return false;
69+
}
70+
71+
field = value;
72+
OnPropertyChanged(propertyName);
73+
return true;
74+
}
75+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
using System.ComponentModel;
5+
using System.ComponentModel.DataAnnotations;
6+
using System.Linq;
7+
using System.Reflection;
8+
using System.Runtime.CompilerServices;
9+
10+
using ValidationResult = System.ComponentModel.DataAnnotations.ValidationResult;
11+
12+
namespace RemoteDesktop.Common.Base;
13+
14+
/// <summary>
15+
/// Extends BaseViewModel with support for data validation using DataAnnotations.
16+
/// </summary>
17+
internal abstract class ValidatableViewModel : BaseViewModel, INotifyDataErrorInfo
18+
{
19+
private readonly IDictionary<string, IList<string>> _errors = new Dictionary<string, IList<string>>();
20+
21+
/// <summary>
22+
/// Event raised when the validation errors for a property have changed.
23+
/// </summary>
24+
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
25+
26+
/// <summary>
27+
/// Indicates whether the object has any validation errors.
28+
/// </summary>
29+
public bool HasErrors => _errors.Any();
30+
31+
/// <summary>
32+
/// Gets the validation errors for a specific property.
33+
/// </summary>
34+
public IEnumerable GetErrors(string propertyName)
35+
{
36+
if (string.IsNullOrEmpty(propertyName))
37+
{
38+
return _errors.SelectMany(e => e.Value);
39+
}
40+
41+
return _errors.TryGetValue(propertyName, out var errors) ? errors : Enumerable.Empty<string>();
42+
}
43+
44+
/// <summary>
45+
/// Raises PropertyChanged and validates the changed property.
46+
/// </summary>
47+
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
48+
{
49+
base.OnPropertyChanged(propertyName);
50+
ValidateProperty(propertyName);
51+
}
52+
53+
/// <summary>
54+
/// Validates a single property using DataAnnotation attributes.
55+
/// Updates the internal error collection and raises ErrorsChanged if necessary.
56+
/// </summary>
57+
protected virtual void ValidateProperty(string propertyName)
58+
{
59+
if (string.IsNullOrEmpty(propertyName))
60+
{
61+
return;
62+
}
63+
64+
var propertyInfo = GetType().GetProperty(propertyName);
65+
if (propertyInfo == null)
66+
{
67+
return;
68+
}
69+
70+
var value = propertyInfo.GetValue(this);
71+
var context = new ValidationContext(this)
72+
{
73+
MemberName = propertyName
74+
};
75+
76+
var results = new List<ValidationResult>();
77+
Validator.TryValidateProperty(value, context, results);
78+
79+
if (_errors.ContainsKey(propertyName))
80+
{
81+
_errors.Remove(propertyName);
82+
}
83+
84+
if (results.Any())
85+
{
86+
_errors[propertyName] = [.. results.Select(r => r.ErrorMessage)];
87+
}
88+
89+
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
90+
}
91+
92+
/// <summary>
93+
/// Validates all public properties of the object.
94+
/// Useful when submitting a form or performing global checks.
95+
/// </summary>
96+
public void ValidateAllProperties()
97+
{
98+
foreach (var property in GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
99+
{
100+
ValidateProperty(property.Name);
101+
}
102+
}
103+
}

src/RemoteDesktop/ViewModels/Parameters/DialogParameters.cs renamed to src/RemoteDesktop/Common/Parameters/InputData.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
using System.Collections.Generic;
22

3-
namespace RemoteDesktop.ViewModels.Parameters;
3+
namespace RemoteDesktop.Common.Parameters;
44

5+
/// <summary>
6+
/// Container for passing parameters between ViewModels.
7+
/// </summary>
58
internal class InputData<T>(T value = default, IEnumerable<string> names = default)
69
{
710
public T Value { get; } = value;
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using System;
2+
using System.Windows.Input;
3+
4+
namespace RemoteDesktop.Common;
5+
6+
/// <summary>
7+
/// A command that executes a delegate when Execute is called and determines
8+
/// whether it can execute in the current state using the CanExecute delegate.
9+
/// </summary>
10+
public class RelayCommand(Action<object> execute, Predicate<object> canExecute) : ICommand
11+
{
12+
private readonly Action<object> _execute = execute ?? throw new ArgumentNullException(nameof(execute));
13+
14+
/// <summary>
15+
/// Creates a new instance of RelayCommand.
16+
/// </summary>
17+
/// <param name="execute">The delegate to execute when the command is invoked.</param>
18+
public RelayCommand(Action execute)
19+
: this(_ => execute(), null)
20+
{
21+
}
22+
23+
/// <summary>
24+
/// Creates a new instance of RelayCommand.
25+
/// </summary>
26+
/// <param name="execute">The delegate to execute when the command is invoked.</param>
27+
/// <param name="canExecute">The delegate that determines whether the command can execute.</param>
28+
public RelayCommand(Action execute, Func<bool> canExecute)
29+
: this(_ => execute(), _ => canExecute())
30+
{
31+
}
32+
33+
/// <summary>
34+
/// Creates a new instance of RelayCommand.
35+
/// </summary>
36+
/// <param name="execute">The delegate to execute when the command is invoked.</param>
37+
public RelayCommand(Action<object> execute)
38+
: this(execute, null)
39+
{
40+
}
41+
42+
/// <summary>
43+
/// Occurs when changes occur that affect whether the command can execute.
44+
/// </summary>
45+
public event EventHandler CanExecuteChanged
46+
{
47+
add => CommandManager.RequerySuggested += value;
48+
remove => CommandManager.RequerySuggested -= value;
49+
}
50+
51+
/// <summary>
52+
/// Determines whether the command can execute in its current state.
53+
/// </summary>
54+
/// <param name="parameter">The parameter for the command.</param>
55+
/// <returns>true if the command can be executed; otherwise false.</returns>
56+
public bool CanExecute(object parameter) => canExecute?.Invoke(parameter) ?? true;
57+
58+
/// <summary>
59+
/// Executes the command with the given parameter.
60+
/// </summary>
61+
/// <param name="parameter">The parameter for the command.</param>
62+
public void Execute(object parameter) => _execute(parameter);
63+
64+
/// <summary>
65+
/// Notifies that the ability of the command to execute has changed.
66+
/// </summary>
67+
public void RaiseCanExecuteChanged() => CommandManager.InvalidateRequerySuggested();
68+
}

0 commit comments

Comments
 (0)