diff --git a/src/Irihi.Avalonia.Shared.Public/Common/IRIHI_CommandBase.cs b/src/Irihi.Avalonia.Shared.Public/Common/IRIHI_CommandBase.cs
new file mode 100644
index 0000000..6c9352a
--- /dev/null
+++ b/src/Irihi.Avalonia.Shared.Public/Common/IRIHI_CommandBase.cs
@@ -0,0 +1,42 @@
+using System.Runtime.CompilerServices;
+using System.Windows.Input;
+
+namespace Irihi.Avalonia.Shared.Common;
+
+///
+/// This is a ICommand implementation for internal use, to avoid irrelevant dependencies.
+/// This should not be used by your application.
+///
+public class IRIHI_CommandBase: ICommand
+{
+ private readonly Action _execute;
+ private readonly Func? _canExecute;
+ public event EventHandler? CanExecuteChanged;
+
+ public IRIHI_CommandBase(Action execute)
+ {
+ _execute = execute;
+ }
+
+ public IRIHI_CommandBase(Action execute, Func canExecute)
+ {
+ _execute = execute;
+ _canExecute = canExecute;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool CanExecute(object parameter)
+ {
+ return _canExecute is null || _canExecute.Invoke();
+ }
+
+ public void Execute(object parameter)
+ {
+ _execute.Invoke();
+ }
+
+ public void NotifyCanExecuteChanged()
+ {
+ CanExecuteChanged?.Invoke(this, EventArgs.Empty);
+ }
+}
\ No newline at end of file
diff --git a/src/Irihi.Avalonia.Shared.Public/Common/IRIHI_CommandBase`T.cs b/src/Irihi.Avalonia.Shared.Public/Common/IRIHI_CommandBase`T.cs
new file mode 100644
index 0000000..238905d
--- /dev/null
+++ b/src/Irihi.Avalonia.Shared.Public/Common/IRIHI_CommandBase`T.cs
@@ -0,0 +1,81 @@
+using System.Runtime.CompilerServices;
+using System.Windows.Input;
+
+namespace Irihi.Avalonia.Shared.Common;
+
+///
+/// This is a ICommand implementation for internal use, to avoid irrelevant dependencies.
+/// This should not be used by your application.
+///
+public class IRIHI_CommandBase: ICommand
+{
+ private readonly Action _execute;
+ private readonly Predicate? _canExecute;
+ public event EventHandler? CanExecuteChanged;
+
+ public IRIHI_CommandBase(Action execute)
+ {
+ _execute = execute;
+ }
+
+ public IRIHI_CommandBase(Action execute, Predicate canExecute)
+ {
+ _execute = execute;
+ _canExecute = canExecute;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool CanExecute(T? parameter)
+ {
+ return _canExecute is null || _canExecute.Invoke(parameter);
+ }
+
+ public bool CanExecute(object parameter)
+ {
+ if (!TryGetCommandArgument(parameter, out var result))
+ {
+ throw new ArgumentException();
+ }
+ return CanExecute(result);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Execute(T? parameter)
+ {
+ _execute(parameter);
+ }
+
+ public void Execute(object parameter)
+ {
+ if (!TryGetCommandArgument(parameter, out var result))
+ {
+ throw new ArgumentException();
+ }
+ Execute(result);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static bool TryGetCommandArgument(object? parameter, out T? result)
+ {
+ if (parameter is null && default(T) is null)
+ {
+ result = default(T);
+ return true;
+ }
+ if (parameter is T obj)
+ {
+ result = obj;
+ return true;
+ }
+ result = default (T);
+ return false;
+ }
+
+ public void NotifyCanExecuteChanged()
+ {
+ EventHandler canExecuteChanged = this.CanExecuteChanged;
+ if (canExecuteChanged is null)
+ return;
+ canExecuteChanged((object) this, EventArgs.Empty);
+ }
+}
\ No newline at end of file
diff --git a/src/Irihi.Avalonia.Shared.Public/Common/IRIHI_ObservableBase.cs b/src/Irihi.Avalonia.Shared.Public/Common/IRIHI_ObservableBase.cs
new file mode 100644
index 0000000..54f6082
--- /dev/null
+++ b/src/Irihi.Avalonia.Shared.Public/Common/IRIHI_ObservableBase.cs
@@ -0,0 +1,36 @@
+using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+
+namespace Irihi.Avalonia.Shared.Common;
+
+///
+/// This is a INotifyPropertyChanged and INotifyPropertyChanging implementation for internal use, to avoid irrelevant dependencies.
+/// This should not be used by your application.
+///
+public class IRIHI_ObservableBase: INotifyPropertyChanged, INotifyPropertyChanging
+{
+
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ public event PropertyChangingEventHandler? PropertyChanging;
+
+ protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
+ {
+ PropertyChanged ?.Invoke((object) this, e);
+ }
+
+ protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
+ {
+ this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
+ }
+
+ protected bool SetProperty(ref T field, T newValue, [CallerMemberName] string? propertyName = null)
+ {
+ if (EqualityComparer.Default.Equals(field, newValue))
+ return false;
+ field = newValue;
+ this.OnPropertyChanged(propertyName);
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/Irihi.Avalonia.Shared.Public/Irihi.Avalonia.Shared.Public.projitems b/src/Irihi.Avalonia.Shared.Public/Irihi.Avalonia.Shared.Public.projitems
index e07f2d7..26949ed 100644
--- a/src/Irihi.Avalonia.Shared.Public/Irihi.Avalonia.Shared.Public.projitems
+++ b/src/Irihi.Avalonia.Shared.Public/Irihi.Avalonia.Shared.Public.projitems
@@ -9,6 +9,9 @@
Irihi.Avalonia.Shared.ShareProject
+
+
+
diff --git a/test/Irihi.Avalonia.Shared.UnitTest.Public/Common/IRIHI_CommandBaseGenericTests.cs b/test/Irihi.Avalonia.Shared.UnitTest.Public/Common/IRIHI_CommandBaseGenericTests.cs
new file mode 100644
index 0000000..cef35d3
--- /dev/null
+++ b/test/Irihi.Avalonia.Shared.UnitTest.Public/Common/IRIHI_CommandBaseGenericTests.cs
@@ -0,0 +1,116 @@
+using System;
+using Xunit;
+
+namespace Irihi.Avalonia.Shared.Common.Tests;
+
+public class IRIHI_CommandBaseGenericTests
+{
+ [Fact]
+ public void ExecuteInvokesActionWithParameter()
+ {
+ int executedParam = 0;
+ var command = new IRIHI_CommandBase(param => executedParam = param);
+
+ command.Execute(5);
+
+ Assert.Equal(5, executedParam);
+ }
+
+ [Fact]
+ public void CanExecuteReturnsTrueByDefault()
+ {
+ var command = new IRIHI_CommandBase