Как отложить выполнение кнопки

В WPF нормальное срабатывание команды кнопки происходит немедленно; но иногда нам нужно предотвратить ложное срабатывание при непреднамеренном нажатии; Итак, как нажать и удерживать кнопку в течение некоторого времени, например 1 с или 500 мс, а затем вызвать команду привязки.

Я надеюсь, что это будет работать как поведение, просто в Xaml для настройки времени действия кнопки.

Большое спасибо, BionicCode! Вдохновленный вами, я немного меняю, и все работает хорошо:

    public class DelayButton : Button
    {
        /// <summary>
        /// Delay Milliseconds
        /// </summary>
        public int DelayInMilliseconds
        {
            get => (int)GetValue(DelayInMillisecondsProperty);
            set => SetValue(DelayInMillisecondsProperty, value);
        }

        public static readonly DependencyProperty DelayInMillisecondsProperty = DependencyProperty.Register(
          nameof(DelayInMilliseconds),
          typeof(int),
          typeof(DelayButton),
          new PropertyMetadata(0));

        /// <summary>
        /// Delay Command
        /// </summary>
        public ICommand DelayCommand
        {
            get { return (ICommand)GetValue(DelayCommandProperty); }
            set { SetValue(DelayCommandProperty, value); }
        }

        public static readonly DependencyProperty DelayCommandProperty = DependencyProperty.Register("DelayCommand",
            typeof(ICommand),
            typeof(DelayButton),
            new PropertyMetadata(null));

        /// <summary>
        /// cts
        /// </summary>
        private CancellationTokenSource? delayCancellationTokenSource;

        /// <summary>
        /// Trigger flag
        /// </summary>
        private bool isClickValid = false;


        /// <summary>
        /// Mouse Down Event
        /// </summary>
        /// <param name = "e"></param>
        protected override async void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            if (DelayInMilliseconds <= 0)
            {
                //One press,one trigger
                this.isClickValid = true;

                //Execute DelayCommand
                DelayCommand?.Execute(this);

                return;
            }

            try
            {
                this.delayCancellationTokenSource = new CancellationTokenSource();

                await Task.Delay(TimeSpan.FromMilliseconds(this.DelayInMilliseconds), this.delayCancellationTokenSource.Token);

                //cheack mouse and not triggr
                if (e.LeftButton is MouseButtonState.Pressed && !this.isClickValid)
                {
                    //One press,one trigger
                    this.isClickValid = true;

                    //Execute DelayCommand
                    DelayCommand?.Execute(this);
                }
            }
            catch (OperationCanceledException)
            {
                return;
            }
            finally
            {
                this.delayCancellationTokenSource?.Dispose();
                this.delayCancellationTokenSource = null;
            }
        }
        
        /// <summary>
        /// Mouse Up Event
        /// </summary>
        /// <param name = "e"></param>
        protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
        {
            if (!this.isClickValid)
            {
                //time not enough
                this.delayCancellationTokenSource?.Cancel();
            }
            else
            {
                //time enough,For Next
                this.isClickValid = false;
            }
        }

        /// <summary>
        /// Mouse Leave Event
        /// </summary>
        /// <param name = "e"></param>
        protected override void OnMouseLeave(MouseEventArgs e)
        {
            this.delayCancellationTokenSource?.Cancel();
        }
    }
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
97
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы можете использовать Task.Delay и отмену задачи, чтобы отложить действия кнопки.

Чтобы фактически отменить дребезг кнопки (интерпретировать несколько щелчков в течение определенного периода времени как один щелчок), вам придется удалить часть отмены и защитить обработчики событий от повторного входа (или использовать таймер, который можно запустить только один раз в течение периода устранения дребезга). Но судя по вашему вопросу, вам просто нужна простая отсрочка.

С точки зрения UX задержка нажатия кнопки значительно ухудшает пользовательский опыт. Это не то поведение, которое пользователь ожидает от кнопки. И не интуитивно понятно, что вам нужно удерживать кнопку нажатой в течение неизвестного времени, чтобы вызвать действие. Я бы посчитал это запахом дизайна пользовательского интерфейса.

Если кнопка будет вызывать важные действия, вам следует рассмотреть возможность отображения диалогового окна подтверждения («Вы уверены?» — «ОК» или «Отмена»). Тогда отмена этого диалогового окна может восстановить исходные значения (или просто проглотить действие кнопки).
Я думаю, что это гораздо лучше (с точки зрения UX), чем использование кнопки с задержкой.
Кнопки — «нажми и забудь». Существуют хорошо известные исключения, когда удержание кнопки (например, RepeatButton) будет постоянно увеличивать значение, например. значение даты. В любом другом сценарии кнопка должна выполняться при нажатии, где продолжительность нажатого состояния не имеет значения.

В следующем примере создается пользовательский DelayButton, который поддерживает все режимы щелчка (нажатие, отпускание, наведение), а также ввод с клавиатуры (пробел и ввод), и который задерживает событие Button.Click и вызов Button.Command.

Он также показывает визуальную обратную связь, чтобы дать пользователю понять, как долго ему нужно нажимать кнопку (полоса «заряжается», чтобы заполнить кнопку, и меняет цвет с красного на зеленый, когда задержка истечет и действие начнется:

<!-- Use DelayInMilliseconds = "0" to use it as a normal button -->
<DelayButton DelayInMilliseconds = "1000" />
public class DelayButton : Button
{
  public int DelayInMilliseconds
  {
    get => (int)GetValue(DelayInMillisecondsProperty);
    set => SetValue(DelayInMillisecondsProperty, value);
  }

  public static readonly DependencyProperty DelayInMillisecondsProperty = DependencyProperty.Register(
    nameof(DelayInMilliseconds),
    typeof(int),
    typeof(DelayButton),
    new PropertyMetadata(0, OnDelayInMillisecondsChanged));

  private static void OnDelayInMillisecondsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    => ((DelayButton)d).UpdateProgressAnimation();

  public SolidColorBrush ProgressBrush
  {
    get => (SolidColorBrush)GetValue(ProgressBrushProperty);
    set => SetValue(ProgressBrushProperty, value);
  }

  public static readonly DependencyProperty ProgressBrushProperty = DependencyProperty.Register(
    nameof(ProgressBrush),
    typeof(SolidColorBrush),
    typeof(DelayButton),
    new PropertyMetadata(Brushes.DarkRed, OnProgressBrushChanged));

  private static void OnProgressBrushChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    => ((DelayButton)d).UpdateProgressAnimation();

  private CancellationTokenSource? delayCancellationTokenSource;
  private bool isClickValid;
  private bool isExecutingKeyAction;
  private bool isExecutingMouseAction;
  private int reentrancyCounter;
  private ProgressBar part_ProgressBar;
  private Storyboard progressStoryBoard;

  static DelayButton()
  {
    DefaultStyleKeyProperty.OverrideMetadata(typeof(DelayButton), new FrameworkPropertyMetadata(typeof(DelayButton)));
  }

  public DelayButton()
  {
    this.ClickMode = ClickMode.Press;
    this.progressStoryBoard = new Storyboard()
    {
      FillBehavior = FillBehavior.HoldEnd,
    };
    UpdateProgressAnimation();
  }

  private void UpdateProgressAnimation()
  {
    if (this.progressStoryBoard.IsFrozen)
    {
      this.progressStoryBoard = this.progressStoryBoard.Clone();
    }

    var delayDuration = TimeSpan.FromMilliseconds(this.DelayInMilliseconds);
    var progressAnimation = new DoubleAnimation(0, 100, new Duration(delayDuration), FillBehavior.HoldEnd);
    Storyboard.SetTargetName(progressAnimation, "PART_ProgressBar");
    Storyboard.SetTargetProperty(progressAnimation, new PropertyPath(ProgressBar.ValueProperty));
    this.progressStoryBoard.Children.Add(progressAnimation);

    var colorAnimation = new ColorAnimation(this.ProgressBrush.Color, Colors.Green, new Duration(delayDuration), FillBehavior.HoldEnd);
    Storyboard.SetTargetName(colorAnimation, "PART_ProgressBar");
    Storyboard.SetTargetProperty(colorAnimation, new PropertyPath("(0).(1)", Control.ForegroundProperty, SolidColorBrush.ColorProperty));
    this.progressStoryBoard.Children.Add(colorAnimation);

    this.progressStoryBoard.Freeze();
  }

  public override void OnApplyTemplate()
  {
    base.OnApplyTemplate();
    this.part_ProgressBar = GetTemplateChild("PART_ProgressBar") as ProgressBar;
  }

  protected override async void OnMouseLeftButtonDown(MouseButtonEventArgs e)
  {
    if (this.isExecutingKeyAction || this.ClickMode is ClickMode.Hover)
    {
      return;
    }

    this.isExecutingMouseAction = true;
    _ = Focus();
    _ = Mouse.Capture(this);

    try
    {
      this.delayCancellationTokenSource = new CancellationTokenSource();
      await DelayActionAsync(this.delayCancellationTokenSource.Token);
      base.OnMouseLeftButtonDown(e);
    }
    catch (OperationCanceledException)
    {
      return;
    }
    finally
    {
      this.delayCancellationTokenSource?.Dispose();
      this.delayCancellationTokenSource = null;
    }
  }

  protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
  {
    if (this.isExecutingKeyAction || this.ClickMode is ClickMode.Hover)
    {
      return;
    }

    this.delayCancellationTokenSource?.Cancel();
    StopProgressAnimation();

    if (this.ClickMode is ClickMode.Release)
    {
      if (!this.isClickValid)
      {
        _ = Mouse.Capture(null);
        this.isExecutingMouseAction = false;
        return;
      }
    }

    base.OnMouseLeftButtonUp(e);
    this.isClickValid = false;
    this.isExecutingMouseAction = false;
  }

  protected override async void OnMouseEnter(MouseEventArgs e)
  {
    if (this.isExecutingKeyAction)
    {
      return;
    }

    if (this.ClickMode is ClickMode.Hover)
    {
      try
      {
        this.isExecutingMouseAction = true;
        this.delayCancellationTokenSource = new CancellationTokenSource();
        await DelayActionAsync(this.delayCancellationTokenSource.Token);
      }
      catch (OperationCanceledException)
      {
        return;
      }
      finally
      {
        this.delayCancellationTokenSource?.Dispose();
        this.delayCancellationTokenSource = null;
      }
    }

    base.OnMouseEnter(e);
  }

  protected override void OnMouseLeave(MouseEventArgs e)
  {
    if (this.isExecutingKeyAction)
    {
      return;
    }

    if (this.ClickMode is ClickMode.Hover)
    {
      this.delayCancellationTokenSource?.Cancel();
      StopProgressAnimation();
      this.isExecutingMouseAction = false;
    }

    base.OnMouseLeave(e);
  }

  protected override async void OnKeyDown(KeyEventArgs e)
  {
    if (this.ClickMode is ClickMode.Hover)
    {
      return;
    }

    if (e.Key is Key.Enter or Key.Space)
    {
      if (this.isExecutingMouseAction || this.reentrancyCounter > 0)
      {
        return;
      }

      try
      {
        this.reentrancyCounter++;
        this.isExecutingKeyAction = true;
        this.delayCancellationTokenSource = new CancellationTokenSource();
        await DelayActionAsync(this.delayCancellationTokenSource.Token);

      }
      catch (OperationCanceledException)
      {
        return;
      }
      finally
      {
        this.delayCancellationTokenSource?.Dispose();
        this.delayCancellationTokenSource = null;
      }
    }

    base.OnKeyDown(e);
  }

  protected override void OnKeyUp(KeyEventArgs e)
  {
    if (this.ClickMode is ClickMode.Hover)
    {
      return;
    }

    if (e.Key is Key.Enter or Key.Space)
    {
      if (this.isExecutingMouseAction)
      {
        return;
      }

      this.delayCancellationTokenSource?.Cancel();
      StopProgressAnimation();
      this.reentrancyCounter--;

      if (this.ClickMode is ClickMode.Release)
      {
        if (!this.isClickValid)
        {
          this.isExecutingKeyAction = false;
          return;
        }
      }
    }

    base.OnKeyUp(e);
    this.isClickValid = false;
    this.isExecutingKeyAction = false;
  }

  private async Task DelayActionAsync(CancellationToken cancellationToken)
  {
    var delayDuration = TimeSpan.FromMilliseconds(this.DelayInMilliseconds);
    cancellationToken.ThrowIfCancellationRequested();
    StartProgressAnimation();
    await Task.Delay(delayDuration, cancellationToken);
    this.isClickValid = true;
  }

  private void StartProgressAnimation()
  {
    if (this.part_ProgressBar is null)
    {
      return;
    }

    this.progressStoryBoard.Begin(this.part_ProgressBar, isControllable: true);
  }

  private void StopProgressAnimation()
  {
    if (this.part_ProgressBar is null)
    {
      return;
    }

    this.progressStoryBoard.Stop(this.part_ProgressBar);
    this.progressStoryBoard.Remove(this.part_ProgressBar);
  }
}

Общий.xaml

<ResourceDictionary xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local = "clr-namespace:WpfApp1">

  <Style x:Key = "FocusVisual">
    <Setter Property = "Control.Template">
      <Setter.Value>
        <ControlTemplate>
          <Rectangle Margin = "2"
                     StrokeDashArray = "1 2"
                     Stroke = "{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"
                     SnapsToDevicePixels = "true"
                     StrokeThickness = "1" />
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>
  
  <SolidColorBrush x:Key = "Button.Static.Background"
                   Color = "#FFDDDDDD" />
  <SolidColorBrush x:Key = "Button.Static.Border"
                   Color = "#FF707070" />
  <SolidColorBrush x:Key = "Button.MouseOver.Background"
                   Color = "#FFBEE6FD" />
  <SolidColorBrush x:Key = "Button.MouseOver.Border"
                   Color = "#FF3C7FB1" />
  <SolidColorBrush x:Key = "Button.Pressed.Background"
                   Color = "#FFC4E5F6" />
  <SolidColorBrush x:Key = "Button.Pressed.Border"
                   Color = "#FF2C628B" />
  <SolidColorBrush x:Key = "Button.Disabled.Background"
                   Color = "#FFF4F4F4" />
  <SolidColorBrush x:Key = "Button.Disabled.Border"
                   Color = "#FFADB2B5" />
  <SolidColorBrush x:Key = "Button.Disabled.Foreground"
                   Color = "#FF838383" />

  <Style TargetType = "local:DelayButton">
    <Setter Property = "FocusVisualStyle"
            Value = "{StaticResource FocusVisual}" />
    <Setter Property = "Background"
            Value = "{StaticResource Button.Static.Background}" />
    <Setter Property = "BorderBrush"
            Value = "{StaticResource Button.Static.Border}" />
    <Setter Property = "Foreground"
            Value = "{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" />
    <Setter Property = "BorderThickness"
            Value = "1" />
    <Setter Property = "HorizontalContentAlignment"
            Value = "Center" />
    <Setter Property = "VerticalContentAlignment"
            Value = "Center" />
    <Setter Property = "Padding"
            Value = "1" />
    <Setter Property = "Template">
      <Setter.Value>
        <ControlTemplate TargetType = "{x:Type local:DelayButton}">
          <Border x:Name = "border"
                  Background = "{TemplateBinding Background}"
                  BorderBrush = "{TemplateBinding BorderBrush}"
                  BorderThickness = "{TemplateBinding BorderThickness}"
                  SnapsToDevicePixels = "true">
            <Grid>
              <ContentPresenter x:Name = "contentPresenter"
                                Focusable = "False"
                                HorizontalAlignment = "{TemplateBinding HorizontalContentAlignment}"
                                Margin = "{TemplateBinding Padding}"
                                RecognizesAccessKey = "True"
                                SnapsToDevicePixels = "{TemplateBinding SnapsToDevicePixels}"
                                VerticalAlignment = "{TemplateBinding VerticalContentAlignment}" />
              <ProgressBar x:Name = "PART_ProgressBar"
                           Opacity = "0.5"
                           Value = "0"
                           Background = "Transparent"
                           Foreground = "{TemplateBinding ProgressBrush}" />
            </Grid>
          </Border>
          <ControlTemplate.Triggers>
            <Trigger Property = "IsDefaulted"
                     Value = "true">
              <Setter Property = "BorderBrush"
                      TargetName = "border"
                      Value = "{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" />
            </Trigger>
            <Trigger Property = "IsMouseOver"
                     Value = "true">
              <Setter Property = "Background"
                      TargetName = "border"
                      Value = "{StaticResource Button.MouseOver.Background}" />
              <Setter Property = "BorderBrush"
                      TargetName = "border"
                      Value = "{StaticResource Button.MouseOver.Border}" />
            </Trigger>
            <Trigger Property = "IsPressed"
                     Value = "true">
              <Setter Property = "Background"
                      TargetName = "border"
                      Value = "{StaticResource Button.Pressed.Background}" />
              <Setter Property = "BorderBrush"
                      TargetName = "border"
                      Value = "{StaticResource Button.Pressed.Border}" />
            </Trigger>
            <Trigger Property = "IsEnabled"
                     Value = "false">
              <Setter Property = "Background"
                      TargetName = "border"
                      Value = "{StaticResource Button.Disabled.Background}" />
              <Setter Property = "BorderBrush"
                      TargetName = "border"
                      Value = "{StaticResource Button.Disabled.Border}" />
              <Setter Property = "TextElement.Foreground"
                      TargetName = "contentPresenter"
                      Value = "{StaticResource Button.Disabled.Foreground}" />
            </Trigger>
          </ControlTemplate.Triggers>
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>
</ResourceDictionary>

Спасибо, BionicCode. Ваш код хорош, я тестирую, в нем есть небольшая ошибка, например, я установил DelayInMilli Seconds = "1000", не только нажал 1 с, но и отпустил кнопку мыши, а затем выполнил команду Trigger. Я бы хотел просто нажать 1 с, а затем команду Trigger.

ZhuYajun 22.07.2024 15:33

Я объясняю, почему мне нужна кнопка с командой запуска с задержкой, потому что некоторые команды кнопок должны запускаться осторожно, но не нуждаются в dailog (1-й диалог будет удерживать программу или скрывать некоторую область пользовательского интерфейса, второй dailog нужно, чтобы человек читал больше и нажимал больше, если Я говорю человеку, что нужно просто нажать на какую-нибудь специальную кнопку в стиле пользовательского интерфейса, хаха)

ZhuYajun 22.07.2024 15:40

«поскольку я установил DelayInMilliсекунды = 1000», не только нажимаю с помощью 1 с, но и отпускаю кнопку мыши, затем запускаю команду. Я бы хотел просто нажимать с помощью 1 с, а затем запускать команду». - Извините, но я вас не понял. Прямо сейчас мне нужно нажать кнопку на 1 секунду, после чего она выполнит команду (все еще нажимая). Вам не нужно отпускать кнопку. Вот как обстоят дела на данный момент. Означает ли это, что вы хотите удерживать 1 с, затем отпустить и затем выполнить команду? Вы хотите сработать при выпуске? Затем просто установите Button.ClickMode на ClickMode.Release: <DelayButton ClickMode = "Release" ... />.

BionicCode 22.07.2024 16:23

<local:DelayButton Height = "50" Margin = "10" Content = "Change Id 1000" DelayInMilli Seconds = "1000" Command = "{Binding ChangeIdCommand}"/> Ваш код Я проверяю его, если использовать привязку команды, нужно освободить кнопка.

ZhuYajun 23.07.2024 11:26

Хорошо спасибо. Я посмотрю. Пожалуйста, подождите.

BionicCode 23.07.2024 14:02

Хорошо. Все работало корректно (без ошибок). Дело в том, что по умолчанию для Button.ClickMode установлено значение Release. Когда я тестировал код, я явно установил для него значение Pressed (я тестировал все режимы). Теперь я установил ClickMode на Pressed из конструктора, поэтому вам не нужно выполнять дополнительную настройку. Я также добавил анимацию прогресса, которая показывает, как долго пользователю придется удерживать кнопку, пока не начнется щелчок. Скопируйте полную новую версию и добавьте стили в файл /Themes/.Generic.xaml вашего проекта. Пожалуйста, дайте мне знать, если возникнут какие-либо проблемы.

BionicCode 23.07.2024 14:24

@ZhuYajun Для полноты картины я добавил поддержку клавиатуры. Теперь нажатие кнопки с использованием пробела или ввода также вызовет задержку и анимацию. И не забудьте настроить пространства имен в стилях (Genric.xaml).

BionicCode 23.07.2024 15:19

Спасибо, вы добавляете стили в визуальное представление, это очень приятно при его использовании. Но есть несколько проблем: если я нажму времени недостаточно, но команда все равно сработает. Если я использую клавиатуру пробела, вы щелкнете в любом месте, чтобы вызвать команду . Для удобства мы можем изменить код здесь:github.com/ZhuYajunFly/WpfApp2.git

ZhuYajun 24.07.2024 04:04

Спасибо за ценный отзыв и потраченное время на создание репозитория. Я обновил ответ, чтобы показать последнюю версию DelayButton. Если есть еще ошибки, пожалуйста, дайте мне знать. Если вы довольны, сообщите мне, чтобы мы могли удалить этот разговор. Я добавил больше логики для управления потоком и изоляции путей ввода. Теперь все должно работать так, как ожидалось.

BionicCode 24.07.2024 12:15

Чтобы обеспечить правильную настройку, я рекомендую создать папку «/Themes» в корне вашего проекта и добавить в нее файл «Generic.xaml». Затем объедините файл DelayButton.xaml (словарь ресурсов) со словарем Generic.xaml.

BionicCode 24.07.2024 12:18

Замечательно! Почти идеально, если добавить две кнопки задержки или больше кнопок, вы обнаружите, что только при нажатии на них достаточно времени появится «Фокус», затем используйте клавишу пробела, чтобы активировать его, в противном случае активируйте старую кнопку, нажмите кнопку достаточно времени. Вы можете быстро проверьте это github.com/ZhuYajunFly/WpfApp2.git

ZhuYajun 24.07.2024 15:53

Я обновил код. Пожалуйста, попробуйте еще раз (добавлен вызов Focus() и улучшены некоторые детали).

BionicCode 24.07.2024 19:49

Удивительно и идеально! Спасибо BionicCode, вы создали его гениально (прогресс с задержкой). Я думаю, что все больше и больше людей захотят его использовать. Я предлагаю вам отправить этот код в MaterialDesignInXamlToolkit и HandyControl, возможно, это поможет большему количеству людей! Еще раз спасибо, BionicCode.

ZhuYajun 25.07.2024 03:25

Другие вопросы по теме