Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Правила для GitHub Copilot

- Всегда отвечай, используя русский язык
- Всегда пиши комментарии в коде на русском языке

## Комментарии
- Короткие пояснительные комментарии располагай в конце той же строки, что и код // кратко по делу
- Старайся избегать тривиальных комментариев

## XML‑документация
- Документируй классы, структуры, делегаты, перечисления и их члены только XML‑комментариями
- Одинарное предложение пиши в одной строке внутри тега и без точки в конце
- Каждый тег XML‑комментария располагай на отдельной строке
- Порядок тегов: `<summary>` → `<param>` → `<returns>` → `<exception>` → `<remarks>` → `<example>`
- Для сложных публичных метдов генерируй блок с простым примером использования кода внутри тега `<example>`

Примеры:
- `<summary>Краткое описание сущности</summary>`
- `<param name="Value">Описание параметра</param>`
- `<returns>Описание возвращаемого значения</returns>`

## Синтаксис и минимализм
- При генерации кода используй современные конструкции языка, совместимые с целевыми платформами проекта
- Стремись минимизировать количество фигурных скобок за счёт expression‑bodied членов и switch‑выражений
- Не убирай фигурные скобки в многострочных конструкциях ради читаемости
- Всегда старайся минимизировать размер кода, если не запрошено иное

Разрешённые современные приёмы (когда поддерживается целевой платформой):
- file‑scoped namespace
- expression‑bodied члены
- switch‑выражения и pattern matching
- target‑typed `new`
- collection expressions и инициализаторы коллекций
- `using var` и `await using`
- операторы `??`, `??=`, `is not`, `with`
- упрощение nullable-присвоения `target?.Property = 15;` вместо `if(target is not null) target.Property = 15;`

## Именование
- Локальные переменные: `snake_case`
- Параметры методов: `PascalCase`
- Поля экземпляров: `_PascalCase`
- Статические поля: `__PascalCase`
- Константы: `PascalCase`
- Публичные типы и члены API: `PascalCase`
- Предпочитай английский язык при именовании переменных, методов, классов и прочих сущностей

## Инициализация и объявления
- При инициализации массивов, списков и словарей используй выражения инициализации массивов/коллекций
- При объявлении переменных предпочитай использовать ключевое слово `var` (кроме случаев, когда явный тип заметно повышает понятность)

## Форматирование
- Короткие системные комментарии пиши компактно в одну строку
- Удаляй неиспользуемые `using`, сортируй и группируй директивы `using`
- Разделяй логические блоки пустыми строками по мере необходимости, избегай лишних переносов

## Практики .NET
- Включай `#nullable enable` там, где это поддерживается
- Используй guard‑выражения, например `ArgumentNullException.ThrowIfNull(x)`
- Предпочитай Try‑паттерны для контроля потока вместо исключений
- При генерации метода добавляй в его начале блок проверки входных параметров. Отделяй этот блок пустой строкой от остального тела метода
- При генерации публичных свойств у моделей-представления MVVM (классов, реализующих INotifyPropertyChanged) используй следующий формат (в одну строку):
```csharp
/// <summary>Описание свойства</summary>
public string PropertyName { get; set => Set(ref field, value); }
```
- Для простых лаконичных методов используй expression‑bodied синтаксис, записанный в одну строку.

## Совместимость целей
- В рабочем пространстве используются целевые платформы: `.NET Standard 2.0` и `.NET 10`
- Применяй современные возможности языка и платформы только если они доступны для соответствующей целевой платформы проекта
12 changes: 6 additions & 6 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: Publish NuGet.org

on:
push:
branches:
branches:
- master
paths-ignore:
paths-ignore:
- '.github/workflows/**'
- '**.md'
- '**.docx'
Expand All @@ -18,7 +18,7 @@ env:
jobs:
build:
name: Build
runs-on: windows-latest
runs-on: windows-latest

steps:
- name: Checkout
Expand All @@ -29,7 +29,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 9.0.x
dotnet-version: 10.0.x

- name: Cache NuGet
uses: actions/cache@v4
Expand Down Expand Up @@ -69,7 +69,7 @@ jobs:

steps:
- name: Get artifact
uses: actions/download-artifact@v4.1.8
uses: actions/download-artifact@v5
id: download
with:
name: Release
Expand All @@ -85,7 +85,7 @@ jobs:

steps:
- name: Get artifact
uses: actions/download-artifact@v4.1.8
uses: actions/download-artifact@v5
id: download
with:
name: Release
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 9.0.x
dotnet-version: 10.0.x

- name: Building
run: |
Expand Down
6 changes: 4 additions & 2 deletions MathCore.WPF.sln
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".Service", ".Service", "{F8
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{40F619FF-49AE-493A-846A-FE4D455B9BCB}"
ProjectSection(SolutionItems) = preProject
.github\copilot-instructions.md = .github\copilot-instructions.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{C83B7C5C-5536-4BDA-9447-7B9A6393F30E}"
ProjectSection(SolutionItems) = preProject
.github\workflows\publish-github.yml = .github\workflows\publish-github.yml
.github\workflows\publish-nuget.yml = .github\workflows\publish-nuget.yml
.github\workflows\publish.yml = .github\workflows\publish.yml
.github\workflows\testing.yml = .github\workflows\testing.yml
EndProjectSection
EndProject
Expand Down
4 changes: 3 additions & 1 deletion MathCore.WPF.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=Viewbox/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Xmax/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Xmin/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=_041F_043E_0442_043E_043A_043E_0431_0435_0437_043E_043F_0430_0441_043D_044B_0439/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=_0430_0432_0442_043E_0437_0430_0432_0435_0440_0448_0435_043D_0438_044F/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=_0432_0430_043B_0438_0434_0430_0446_0438_0438/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/UserDictionary/Words/=_0432_0430_043B_0438_0434_0430_0446_0438_0438/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=_043F_043E_0442_043E_043A_043E_0431_0435_0437_043E_043F_0430_0441_043D_043E_0433_043E/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
41 changes: 15 additions & 26 deletions MathCore.WPF/AttachedProperties/DataGridEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,19 @@ public static class DataGridEx
{
#region Attached property DataGrid.UseDataAnnotations : bool - Использовать аннотации данных из пространства имён System.ComponentModel.DataAnnotation

/// <summary>Использовать аннотации данных из пространства имён System.DataAnnotation</summary>
/// <summary>Использовать аннотации данных из пространства имён <see cref="System.ComponentModel"/></summary>
public static readonly DependencyProperty UseDataAnnotationsProperty =
DependencyProperty.RegisterAttached(
"UseDataAnnotations",
typeof(bool),
typeof(DataGridEx),
new(OnUseDataAnnotationsPropertyChanged));

/// <summary>Использовать аннотации данных из пространства имён System.DataAnnotation</summary>
/// <summary>Использовать аннотации данных из пространства имён <see cref="System.ComponentModel.DataAnnotations"/></summary>
[AttachedPropertyBrowsableForType(typeof(DataGrid))]
public static void SetUseDataAnnotations(DependencyObject D, bool value) => D.SetValue(UseDataAnnotationsProperty, value);

/// <summary>Использовать аннотации данных из пространства имён System.DataAnnotation</summary>
/// <summary>Использовать аннотации данных из пространства имён <see cref="System.ComponentModel.DataAnnotations"/></summary>
public static bool GetUseDataAnnotations(DependencyObject D) => (bool)D.GetValue(UseDataAnnotationsProperty);

private static void OnUseDataAnnotationsPropertyChanged(DependencyObject D, DependencyPropertyChangedEventArgs E)
Expand Down Expand Up @@ -59,26 +59,15 @@ private static void OnDataGridGeneratingColumn(object? Sender, DataGridAutoGener

var column = E.Column;

//if (property.PropertyType == typeof(DateTime))
//{
// E.Column = new DataGridTemplateColumn
// {
// HeaderTemplate = column.HeaderTemplate,
// Header = column.Header,
// CellTemplate = new DataTemplate(item_type) { }
// };
// column = E.Column;
//}

var display_attribute = property.GetCustomAttribute<DisplayAttribute>();

if(display_attribute?.GetAutoGenerateField() == false)
if (display_attribute?.GetAutoGenerateField() == false)
{
E.Cancel = true;
return;
}

if((display_attribute?.Name ?? property.GetCustomAttribute<DisplayNameAttribute>()?.DisplayName) is { } name)
if ((display_attribute?.Name ?? property.GetCustomAttribute<DisplayNameAttribute>()?.DisplayName) is { } name)
column.Header = name;

if (display_attribute?.Name is { } description)
Expand All @@ -94,12 +83,12 @@ private static void OnDataGridGeneratingColumn(object? Sender, DataGridAutoGener

if (property.GetCustomAttribute<DisplayFormatAttribute>() is { } format_attribute)
{
var text_column = column as DataGridTextColumn;
var text_column = column as DataGridTextColumn;
var value_format = format_attribute.DataFormatString;
if (value_format != null && text_column != null)
{
var binding = (Binding)text_column.Binding;
binding.StringFormat = value_format;
binding.StringFormat = value_format;
binding.ConverterCulture = Thread.CurrentThread.CurrentUICulture;
}

Expand All @@ -112,17 +101,17 @@ private static void OnDataGridGeneratingColumn(object? Sender, DataGridAutoGener
column.IsReadOnly = column_readonly;

if (property.GetCustomAttribute<ColumnWidthAttribute>() is
{
Width : var col_width,
Auto : var col_auto,
Adaptive: var col_adaptive
})
{
Width: var col_width,
Auto: var col_auto,
Adaptive: var col_adaptive
})
column.Width = (col_width, col_auto, col_adaptive) switch
{
(not double.NaN and var width, false, false) => new DataGridLength(width),
(var width, false, true) => new DataGridLength(width, DataGridLengthUnitType.Star),
(var width, true, _) => new DataGridLength(width, DataGridLengthUnitType.Auto),
_ => new DataGridLength()
(var width, false, true) => new DataGridLength(width, DataGridLengthUnitType.Star),
(var width, true, _) => new DataGridLength(width, DataGridLengthUnitType.Auto),
_ => new DataGridLength()
};
}

Expand Down
6 changes: 3 additions & 3 deletions MathCore.WPF/AttachedProperties/UI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public static class UI
"InputBinding",
typeof(InputBinding),
typeof(UI),
new(default(InputBinding), OnInputBindingChanged));
new(null, OnInputBindingChanged));

/// <summary>Обработчик события изменения значения свойства <see cref="InputBindingProperty"/></summary>
/// <param name="D">Элемент, с которым ассоциирована коллекция горячих клавиш</param>
Expand Down Expand Up @@ -43,14 +43,14 @@ public static class UI
/// <summary>Глобальные горячие клавиши</summary>
public static GlobalHotKeysCollection GetHotKeys(DependencyObject element)
{
if (element.GetValue(HotKeysProperty) is GlobalHotKeysCollection collection)
if (element.GetValue(HotKeysProperty) is GlobalHotKeysCollection collection)
return collection;

collection = [];
if (element is FrameworkElement framework_element)
framework_element.Unloaded += (e, _) =>
{
if(((DependencyObject)e).GetValue(HotKeysProperty) is GlobalHotKeysCollection keys)
if (((DependencyObject)e).GetValue(HotKeysProperty) is GlobalHotKeysCollection keys)
keys.Dispose();
};
SetHotKeys(element, collection);
Expand Down
22 changes: 22 additions & 0 deletions MathCore.WPF/Attributes/MayBeNullAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#if !NET5_0_OR_GREATER
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace System.Diagnostics.CodeAnalysis;

/// <summary>Specifies that <see langword="null" /> is allowed as an input even if the corresponding type disallows it.</summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, Inherited = false)]
public sealed class AllowNullAttribute : Attribute
{
}

/// <summary>Specifies that an output may be <see langword="null" /> even if the corresponding type disallows it.</summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.ReturnValue, Inherited = false)]
public sealed class MaybeNullAttribute : Attribute
{
}

#endif
39 changes: 11 additions & 28 deletions MathCore.WPF/CollectionSelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,50 +13,33 @@ public class CollectionSelector<T>(bool SelectFirstItem = true) : ViewModel
{
#region Items : IEnumerable<T> - Коллекция

/// <summary>Коллекция</summary>
private IEnumerable<T>? _Items;

/// <summary>Коллекция</summary>
public IEnumerable<T>? Items
{
get => _Items;
get;
set
{
if (Set(ref _Items, value))
if (Set(ref field, value))
SelectedItem = value switch
{
T[] { Length : > 0 } array => array[0],
List<T> { Count : > 0 } list => list[0],
LinkedList<T> { First.Value : { } first_value } => first_value,
ObservableCollection<T> { Count: > 0 } collection => collection[0],
IList<T> { Count : > 0 } list => list[0],
{ } items => items.FirstOrDefault(),
_ => default
T[] { Length: > 0 } array => array[0],
List<T> { Count: > 0 } list => list[0],
LinkedList<T> { First.Value: { } first_value } => first_value,
ObservableCollection<T> { Count: > 0 } collection => collection[0],
IList<T> { Count: > 0 } list => list[0],
{ } items => items.FirstOrDefault(),
_ => default
};
}
}

#endregion

#region SelectedItem : T - Выбранный элемент

/// <summary>Выбранный элемент</summary>
private T? _SelectedItem;

/// <summary>Выбранный элемент</summary>
public T? SelectedItem { get => _SelectedItem; set => Set(ref _SelectedItem, value); }

#endregion

#region SelectFirstItem : bool - Выбирать первый элемент для нового значения коллекции

/// <summary>Выбирать первый элемент для нового значения коллекции</summary>
private bool _SelectFirstItem = SelectFirstItem;
public T? SelectedItem { get; set => Set(ref field, value); }

/// <summary>Выбирать первый элемент для нового значения коллекции</summary>
public bool SelectFirstItem { get => _SelectFirstItem; set => Set(ref _SelectFirstItem, value); }

#endregion
public bool SelectFirstItem { get; set => Set(ref field, value); } = SelectFirstItem;

public CollectionSelector(IEnumerable<T> Items, bool SelectFirstItem = true) : this(SelectFirstItem) => this.Items = Items;

Expand Down
4 changes: 2 additions & 2 deletions MathCore.WPF/CollectionViewShaper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace MathCore.WPF;

/// <summary> </summary>
/// <summary>Формирует fluent-api для задания представления коллекции WPF с помощью LINQ-подобного синтаксиса</summary>
/// <remarks>
/// <code>
/// // Collection to which the view is bound
Expand Down Expand Up @@ -128,7 +128,7 @@ public CollectionViewShaper<T> GroupBy<TKey>(Expression<Func<T, TKey>> selector)
private static string GetPropertyPath(Expression expression)
{
var names = new Stack<string>();
var expr = expression;
var expr = expression;
while (expr is not null and not ParameterExpression and not ConstantExpression)
{
if (expr is not MemberExpression member)
Expand Down
Loading
Loading