Форум программистов, компьютерный форум, киберфорум
C#: WPF, UWP и Silverlight
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.68/40: Рейтинг темы: голосов - 40, средняя оценка - 4.68
9 / 7 / 2
Регистрация: 26.12.2018
Сообщений: 278

Валидация данных WPF MVVM

23.05.2020, 21:22. Показов 8357. Ответов 12

Студворк — интернет-сервис помощи студентам
Подскажите, как можно сделать проверку введенных данных пользователя.
У меня есть форма, которую заполняет пользователь. Работаю через EF code first, получается при нажатии на кнопку, у меня происходит вызов команды и данные заносятся в БД. Я создал свой интерфейс, который проверяет валидацию и в случае некоректных данных подсвечивает поле красным цветом:
https://metanit.com/sharp/wpf/14.php
Стандартный пример из метанита.
НО проблема в том, что наша кнопка с командой никак не знает о нашей валидации и светится или нет текстбокс им до фонаря, будь это не MVVM я заблокировал бы кнопку, при нарушении валидации, возможно тут так же можно, просто я не знаю. Подскажите пожалуйста, как можно это сделать.

Добавлено через 45 минут
https://stackoverflow.com/ques... validation
Узнал, что есть такая возможность блокировки кнопки
создал класс
C#
1
2
3
4
5
6
7
8
9
 class NumberValidationRule : ValidationRule
    {
        public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
        {
            int result = 0;
            bool canConvert = int.TryParse(value as string, out result);
            return new ValidationResult(canConvert, "Not a valid double");
        }
    }
в XAML
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
   <Window.CommandBindings>
        <CommandBinding Command="ApplicationCommands.Save" CanExecute="Save_CanExecute" Executed="Save_Executed"/>
    </Window.CommandBindings>
 
   <TextBox  Validation.ErrorTemplate="{StaticResource validationFailed}" x:Name="AgeTextBox"  Height="23" Canvas.Left="82" TextWrapping="Wrap" Canvas.Top="100" Width="120" >
                <TextBox.Text>
                    <Binding Path="Age">
                        <Binding.ValidationRules>
                            <validationrules:NumberValidationRule/>
                            <DataErrorValidationRule/>
                        </Binding.ValidationRules>
                    </Binding>
                </TextBox.Text>
            </TextBox>
 
 
 <Button x:Name="updateButton" Content="Add USER" Canvas.Left="377" Canvas.Top="458" Width="75" Command="{Binding ApplicationCommands.Save.AddUserCommand}"/>
И code behind
C#
1
2
3
4
5
6
7
8
9
private void Save_CanExecute(object sender, CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = IsValid(sender as DependencyObject);
    }
 
private bool IsValid(DependencyObject obj)
    {            
        return !Validation.GetHasError(obj) && LogicalTreeHelper.GetChildren(obj).OfType<DependencyObject>().All(IsValid);
    }
Но почему-то не идет
0
Лучшие ответы (1)
IT_Exp
Эксперт
34794 / 4073 / 2104
Регистрация: 17.06.2006
Сообщений: 32,602
Блог
23.05.2020, 21:22
Ответы с готовыми решениями:

Валидация данных в WPF
Здравствуйте, не могу понять что не так. Есть такой код : &lt;TextBox Grid.Row=&quot;1&quot; Grid.Column=&quot;1&quot; ...

Получения данных из DataGrid WPF MVVM
Добрый день господа форумчане. Возник вопрос, как при использовании паттерна MVVM в WPF при выделении строки в DataGrid получить её...

WPF команды и MVVM. Часть 1. [WPF, Элд Хасп]
Тема из цикла https://www.cyberforum.ru/wpf-silverlight/thread2384523.html Для использования и создания WPF команд в Net предусмотрен...

12
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16137 / 11261 / 2888
Регистрация: 21.04.2018
Сообщений: 33,096
Записей в блоге: 2
23.05.2020, 22:47
Цитата Сообщение от Anton006 Посмотреть сообщение
Но почему-то не идет
Посмотрите пример: Активация кнопки окна регистрации
1
9 / 7 / 2
Регистрация: 26.12.2018
Сообщений: 278
23.05.2020, 23:30  [ТС]
почему-то ругается в viewModel
C#
1
2
3
4
5
6
7
8
9
10
public RelayCommand ConnectCommand => _connectCommand
            ?? (_connectCommand = new RelayCommand(ConnectMethod, ConnectCanMethod));
 
        private bool ConnectCanMethod(object parameter)
            => parameter is bool error && ! error;
 
        private void ConnectMethod(object parameter)
        {
            MessageBox.Show("Соединение");
        }
неудается преобразовать из группы методов в Action

Добавлено через 12 минут
Это обязательный элемент?

Добавлено через 4 минуты
Глупый вопрос)) обязательно
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16137 / 11261 / 2888
Регистрация: 21.04.2018
Сообщений: 33,096
Записей в блоге: 2
23.05.2020, 23:43
Цитата Сообщение от Anton006 Посмотреть сообщение
неудается преобразовать из группы методов в Action
Скорее всего у вас используется иная реализация RelayCommand.
В примере используется эта Новая реализация RelayCommand с исправлениями от proa33 и kolorotur [WPF, Элд Хасп]
0
9 / 7 / 2
Регистрация: 26.12.2018
Сообщений: 278
24.05.2020, 00:01  [ТС]
то есть вместо OnPropertyChangedClass надо RelayCommand.?

Добавлено через 5 минут
а есть альтернативные варианты ? Просто данная реализация сложная для меня и очень многое не понятно
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16137 / 11261 / 2888
Регистрация: 21.04.2018
Сообщений: 33,096
Записей в блоге: 2
24.05.2020, 00:24
Цитата Сообщение от Anton006 Посмотреть сообщение
то есть вместо OnPropertyChangedClass надо RelayCommand.?
НЕТ! Нужны обе.
Новая реализация OnPropertyChangedClass [WPF, Элд Хасп]
Новая реализация RelayCommand с исправлениями от proa33 и kolorotur [WPF, Элд Хасп]

Цитата Сообщение от Anton006 Посмотреть сообщение
а есть альтернативные варианты ? Просто данная реализация сложная для меня и очень многое не понятно
Посмотрю завтра.
1
9 / 7 / 2
Регистрация: 26.12.2018
Сообщений: 278
24.05.2020, 00:36  [ТС]
хорошо, спасибо
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16137 / 11261 / 2888
Регистрация: 21.04.2018
Сообщений: 33,096
Записей в блоге: 2
24.05.2020, 09:34
Цитата Сообщение от Anton006 Посмотреть сообщение
а есть альтернативные варианты ? Просто данная реализация сложная для меня и очень многое не понятно
Anton006, вам придётся немного больше разобраться в WPF.
По сути ваш пример реализации - просто набор бессмысленного кода.
Я вам покажу сейчас несколько вариантов реализации, но вам надо их непросто скопипастить, а понять как они работают.
Иначе... у вас постоянно будут возникать вопросы по простейшим задачам.

В первую очередь, что существенно для вашей задачи, вам надо разобраться с такими темами:
  • Привязки - Binding. Они задаются (по умолчанию) к контексту данных. А к чему у вас вот эта привязка Command="{Binding ApplicationCommands.Save.AddUserCommand}"?

  • Команды: ICommand, RoutedCommand, всплытие команд. Вы ловите в окне команду Command="ApplicationCommands.Save" - это должен быть ОБЯЗАТЕЛЬНО экземпляр RoutedCommand. А в команду кнопки привязываете Command="{Binding ...}" экземпляр ICommand, который НЕ ВСПЛЫВАЕТ!

  • MVVM - WPF очень сильно заточен под паттерн MVVM. В нём выделенно специальное свойство DataContext для источника данных. И без MVVM реализация WPF Решения сильно усложняется.

  • Надо понимать где проходит разделение между ДАННЫМИ и их ПРЕДСТАВЛЕНИЕМ. Допустим, в данной задаче, валидации введённых данных - это валидация Представления или Данных? Это зависит от условий задачи. Если всё неообходимое для валидации есть на уровне Представления, то это задача представления. В вашей задаче надо обеспечить ввод только чисел. Это задача Представления. И решить её проще всего в XAML. Если же надо было проверять число на корректность в БД (частый пример - уникальность ID), то это задача Данных и решать её надо было бы в команде привязанной к ViewModel.
1
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16137 / 11261 / 2888
Регистрация: 21.04.2018
Сообщений: 33,096
Записей в блоге: 2
24.05.2020, 09:50
Anton006, допустим, у вас такая ViewModel
C#
1
2
3
4
    public class ViewModel 
    {
        public string Age { get; set; }
    }
Тогда задачу можно реализовать так:
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    <Window.DataContext>
        <local:ViewModel/>
    </Window.DataContext>
    <Window.CommandBindings>
        <CommandBinding Command="Save" CanExecute="Save_CanExecute" Executed="Save_Executed"/>
    </Window.CommandBindings>
    <StackPanel>
        <TextBox  x:Name="AgeTextBox"  Height="23" Canvas.Left="82" TextWrapping="Wrap" Canvas.Top="100" Width="120" >
            <TextBox.Text>
                <Binding Path="Age" UpdateSourceTrigger="PropertyChanged">
                    <Binding.ValidationRules>
                        <local:NumberValidationRule/>
                    </Binding.ValidationRules>
                </Binding>
            </TextBox.Text>
        </TextBox>
 
 
        <Button x:Name="updateButton" Content="Add USER" Canvas.Left="377" Canvas.Top="458" Width="75"
                Command="Save"
                CommandParameter="{Binding Path=(Validation.HasError), ElementName=AgeTextBox, Mode=OneWay}"/>
    </StackPanel>
</Window>
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
        private void Save_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            //e.CanExecute = IsValid(sender as DependencyObject);
            e.CanExecute = e.Parameter is bool error && !error;
        }
 
        //private bool IsValid(DependencyObject obj)
        //{
        //  return !Validation.GetHasError(obj) && LogicalTreeHelper.GetChildren(obj).OfType<DependencyObject>().All(IsValid);
        //}
 
        private void Save_Executed(object sender, ExecutedRoutedEventArgs e)
        {
 
        }
    }
1
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16137 / 11261 / 2888
Регистрация: 21.04.2018
Сообщений: 33,096
Записей в блоге: 2
24.05.2020, 10:06
Anton006, но использовать параметр команды только ради отправки ошибки - не эфективно.
Можно воспользоваться вот таким конвертером
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using System;
using System.Globalization;
using System.Windows.Data;
 
namespace Common
{
    public class BooleanNotConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            // Проверка на передаваемый тип Boolean
            if (value is bool valBool)
                return !valBool;
            // Проверка на передаваемый тип String
            if (value is string valStr && bool.TryParse(valStr, out valBool))
                return !valBool;
 
            throw new ArgumentException();
        }
 
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            // Проверка на передаваемый тип Boolean
            if (value is bool valBool)
                return !valBool;
            // Проверка на передаваемый тип String
            if (value is string valStr && bool.TryParse(valStr, out valBool))
                return !valBool;
            throw new ArgumentException();
        }
    }
 
}
И сделать так
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    <Window.DataContext>
        <local:ViewModel/>
    </Window.DataContext>
    <Window.CommandBindings>
        <CommandBinding Command="Save" Executed="Save_Executed"/>
    </Window.CommandBindings>
    <Window.Resources>
        <common:BooleanNotConverter x:Key="BooleanNotConverter"/>
    </Window.Resources>
    <StackPanel>
        <TextBox  x:Name="AgeTextBox"  Height="23" Canvas.Left="82" TextWrapping="Wrap" Canvas.Top="100" Width="120" >
            <TextBox.Text>
                <Binding Path="Age" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
                    <Binding.ValidationRules>
                        <local:NumberValidationRule/>
                    </Binding.ValidationRules>
                </Binding>
            </TextBox.Text>
        </TextBox>
 
 
        <Button x:Name="updateButton" Content="Add USER" Canvas.Left="377" Canvas.Top="458" Width="75"
                Command="Save"
                IsEnabled="{Binding Path=(Validation.HasError), ElementName=AgeTextBox, Mode=OneWay, Converter={StaticResource BooleanNotConverter}}"/>
    </StackPanel>
</Window>
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
        //private void Save_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        //{
        //  //e.CanExecute = IsValid(sender as DependencyObject);
        //  e.CanExecute = e.Parameter is bool error && !error;
        //}
 
        //private bool IsValid(DependencyObject obj)
        //{
        //  return !Validation.GetHasError(obj) && LogicalTreeHelper.GetChildren(obj).OfType<DependencyObject>().All(IsValid);
        //}
 
        private void Save_Executed(object sender, ExecutedRoutedEventArgs e)
        {
 
        }
    }
1
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16137 / 11261 / 2888
Регистрация: 21.04.2018
Сообщений: 33,096
Записей в блоге: 2
24.05.2020, 10:18
Лучший ответ Сообщение было отмечено Anton006 как решение

Решение

Anton006, предыдущие варианты работают, но они КОНЦЕПТУАЛЬНО не верны для WPF.
Что делает кнопка?
По нажатию на неё должны быть сохранены ДАННЫЕ.
Значит в данной задаче к кнопке должна быть привязана команда из ViewModel !

Для чего служит свойство Age?
Для сохранения натурального числа.
Значит это свойство VM должно быть не string, а uint.
И тогда в простой валидации на число нет необходимости.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    /// <summary>ViewModel - предназначена для передачи в Контекст Данных окна</summary>
    public class ViewModel : OnPropertyChangedClass
    {
        private RelayCommand _saveCommand;
 
        public int Age { get; set; }
 
        public RelayCommand SaveCommand => _saveCommand
            ?? (_saveCommand = new RelayCommand(SaveMethod));
 
        private void SaveMethod(object parameter)
        {
            // Здесь команды необходимые для выполнения 
            // сохранения данных
        }
    }
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    <Window.DataContext>
        <local:ViewModel/>
    </Window.DataContext>
    <!--<Window.CommandBindings>
        <CommandBinding Command="Save" Executed="Save_Executed"/>
    </Window.CommandBindings>-->
    <Window.Resources>
        <common:BooleanNotConverter x:Key="BooleanNotConverter"/>
    </Window.Resources>
    <StackPanel>
        <TextBox  x:Name="AgeTextBox"  Height="23" Canvas.Left="82" TextWrapping="Wrap" Canvas.Top="100" Width="120"
                  Text="{Binding Age, UpdateSourceTrigger=PropertyChanged}">
            <!--<TextBox.Text>
                <Binding Path="Age" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
                    <Binding.ValidationRules>
                        <local:NumberValidationRule/>
                    </Binding.ValidationRules>
                </Binding>
            </TextBox.Text>-->
        </TextBox>
 
 
        <Button x:Name="updateButton" Content="Add USER" Canvas.Left="377" Canvas.Top="458" Width="75"
                Command="{Binding SaveCommand}"
                IsEnabled="{Binding Path=(Validation.HasError), ElementName=AgeTextBox, Mode=OneWay, Converter={StaticResource BooleanNotConverter}}"/>
    </StackPanel>
</Window>
C#
1
2
3
4
    public partial class MainWindow : Window
    {
        public MainWindow() => InitializeComponent();
    }
1
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16137 / 11261 / 2888
Регистрация: 21.04.2018
Сообщений: 33,096
Записей в блоге: 2
24.05.2020, 10:37
Anton006, также изучите WPF компоновку.
В WPF не используется явное задание размеров и позиции элементов.
Для этого надо использовать компоновку в различные контейнеры, в основном различные Панели.
Чего-чего, а контейнеров в WPF хватает на все случаи жизни.

Пример:

Класс для пользователя
C#
1
2
3
4
5
    public class UserVM
    {
        public int Age { get; set; }
        public string FirsName { get; set; }
    }
ViewModel
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    /// <summary>ViewModel - предназначена для передачи в Контекст Данных окна</summary>
    public class ViewModel : OnPropertyChangedClass
    {
        private RelayCommand _saveCommand;
 
        public UserVM User { get; set; } = new UserVM();
 
        public RelayCommand SaveCommand => _saveCommand
            ?? (_saveCommand = new RelayCommand(SaveMethod, SaveCanMethod));
 
        private bool SaveCanMethod(object parameter)
        {
            return parameter is UserVM;
        }
 
        private void SaveMethod(object parameter)
        {
            UserVM user = (UserVM)parameter;
            // Здесь команды необходимые для выполнения 
            // сохранения данных
        }
    }
XAML
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
    <Window.DataContext>
        <local:ViewModel/>
    </Window.DataContext>
    <!--<Window.CommandBindings>
        <CommandBinding Command="Save" Executed="Save_Executed"/>
    </Window.CommandBindings>-->
    <Window.Resources>
        <common:BooleanNotConverter x:Key="BooleanNotConverter"/>
    </Window.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid DataContext="{Binding User}">
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <TextBlock Text="Имя:" Margin="5"/>
            <TextBlock Grid.Row="1" Text="Возраст:" Margin="5"/>
            <TextBox Grid.Column="1" HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
                     Text="{Binding FirsName, UpdateSourceTrigger=PropertyChanged}" MinWidth="150"/>
            <TextBox  x:Name="tbAge" Grid.Row="1" Grid.Column="1" HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
                  Text="{Binding Age, UpdateSourceTrigger=PropertyChanged}" MinWidth="50"/>
        </Grid>
 
        <Button Grid.Column="1" Padding="20,5" Margin="5" HorizontalAlignment="Center" VerticalAlignment="Center" Content="Add USER"
                Command="{Binding SaveCommand}"
                CommandParameter="{Binding User}"
                IsEnabled="{Binding Path=(Validation.HasError), ElementName=tbAge, Mode=OneWay, Converter={StaticResource BooleanNotConverter}}"/>
    </Grid>
</Window>
1
9 / 7 / 2
Регистрация: 26.12.2018
Сообщений: 278
24.05.2020, 11:26  [ТС]
спасибо за помощь,буду разбираться
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
BasicMan
Эксперт
29316 / 5623 / 2384
Регистрация: 17.02.2009
Сообщений: 30,364
Блог
24.05.2020, 11:26
Помогаю со студенческими работами здесь

Валидация datagrid mvvm
Здравствуйте! Никак не получается реализовать валидацию полей datagrid. много статей перечитал, прошу помощи. Задача: проверять...

WPF команды и MVVM. Часть 2. Всплытие команд. Реализация команды для списка элементов [WPF, Элд Хасп]
Тема из цикла https://www.cyberforum.ru/wpf-silverlight/thread2384523.html На практике часто встречаются случаи когда команда и кнопка...

WPF MVVM
Привет всем! Изучаю патерн MVVM. Не могу понять как реализовать ее, если допустим мне необходимо одно и тоже окно использовать для разных ...

Валидация текстового ввода WPF
Добрый день, отрывок с метанита &lt;TextBox PreviewKeyDown=&quot;TextBox_PreviewKeyDown&quot; /&gt; private void TextBox_PreviewKeyDown(object...

WPF Валидация не убирается текст
Здравствуйте, не могу понять что не так. Текст не убирается и поле красное даже если поле заполнено. В чем тут может быть проблема,...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
13
Ответ Создать тему
Новые блоги и статьи
Ритм жизни
kumehtar 27.02.2026
Иногда приходится жить в ритме, где дел становится всё больше, а вовлечения в происходящее — всё меньше. Плотный график не даёт вниманию закрепиться ни на одном событии. Утро начинается с быстрых,. . .
[В процессе разработки] SDL3 для Web (WebAssembly): Сборка библиотек SDL3 и Box2D из исходников с помощью CMake и Emscripten
8Observer8 27.02.2026
Недавно вышла версия 3. 4. 2 библиотеки SDL3. На странице официальной релиза доступны исходники, готовые DLL (для x86, x64, arm64), а также библиотеки для разработки под Android, MinGW и Visual Studio. . . .
SDL3 для Web (WebAssembly): Реализация движения на Box2D v3 - трение и коллизии с повёрнутыми стенами
8Observer8 20.02.2026
Содержание блога Box2D позволяет легко создать главного героя, который не проходит сквозь стены и перемещается с заданным трением о препятствия, которые можно располагать под углом, как верхнее. . .
Конвертировать закладки radiotray-ng в m3u-плейлист
damix 19.02.2026
Это можно сделать скриптом для PowerShell. Использование . \СonvertRadiotrayToM3U. ps1 <path_to_bookmarks. json> Рядом с файлом bookmarks. json появится файл bookmarks. m3u с результатом. # Check if. . .
Семь CDC на одном интерфейсе: 5 U[S]ARTов, 1 CAN и 1 SSI
Eddy_Em 18.02.2026
Постепенно допиливаю свою "многоинтерфейсную плату". Выглядит вот так: https:/ / www. cyberforum. ru/ blog_attachment. php?attachmentid=11617&stc=1&d=1771445347 Основана на STM32F303RBT6. На борту пять. . .
Камера Toupcam IUA500KMA
Eddy_Em 12.02.2026
Т. к. у всяких "хикроботов" слишком уж мелкий пиксель, для подсмотра в ESPriF они вообще плохо годятся: уже 14 величину можно рассмотреть еле-еле лишь на экспозициях под 3 секунды (а то и больше),. . .
И ясному Солнцу
zbw 12.02.2026
И ясному Солнцу, и светлой Луне. В мире покоя нет и люди не могут жить в тишине. А жить им немного лет.
«Знание-Сила»
zbw 12.02.2026
«Знание-Сила» «Время-Деньги» «Деньги -Пуля»
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru